【C++研发面试笔记】23. 面向对象设计模式

来源:互联网 发布:骑士夺冠 知乎 编辑:程序博客网 时间:2024/06/06 18:36

23. C++设计模式

面向对象系统的分析和设计实际上追求的就是两点, 一是高内聚(Cohesion),二是低耦合(Coupling)。
面向对象的基本原则:

  1. 开闭原则(对扩展开放;对修改封闭):对于扩展是开放的,对于修改是关闭的,一个好的系统是在不修改源代码的情况下,可以扩展你的功能。而实现开闭原则的关键就是抽象化。
  2. 里氏代换原则:任何基类可以出现的地方,子类也可以出现。
  3. 依赖倒转原则:要依赖抽象,而不要依赖具体的实现。抽象不应当依赖于细节;细节应当依赖于抽象。要针对接口编程,不针对实现编程。
  4. 合成复用原则:要尽量使用合成、聚合,尽量不要使用继承。
  5. 一个对象应当对其它对象有尽可能少的了解。系统中的类,尽量不要与其他类互相作用,减少类之间的耦合度。
  6. 接口隔离法则:使用多个专门的接口比使用单一的总接口总要好。一个类对另外一个类的依赖性应当是建立在最小接口上的。过于臃肿的接口是对接口的污染。不应该强迫客户依赖于它们不用的方法。

23.1 工厂模式(Factory)

23.1.1 解决问题

创建一个生成对象的接口,让子类决定实例化哪一个类。使一个类的实例化延迟到其子类。比如客户需要一台车子,如果我们知道提前了这个车子的型号,那直接实例化这车子就可以了,但很多情况下,写之前并不知道客户到底要什么车子,所以需要创建一个工厂的接口,这个工厂提供了创建不同类的方法。

23.1.2 适用

当一个类不知道它所必须创建的对象的类的时候。
当一个类希望由它的子类来指定它所创建的对象的时候。
当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

23.1.3 问题

Factory类不能是封闭的,比如一但我们新增了车子的型号,同样我们也需要在Factory中添加一个新方法。


23.2 抽象工厂模式

创建一组相关或者相互依赖的对象。抽象工厂相对于工厂更进一步,多了一个工厂抽象类,提供了不同工厂,用于生产不同的产品。
同样是前面的例子,这个时候多了一个品牌厂商的选择。
对于产品来说,有不同的型号,比如小车,卡车、公交车之类
而对于工厂,有不同的品牌,比如大众,长安之类,每个工厂都能生产不同型号的产品。


23.3 Singleton模式

在 Singleton 模式的结构图中可以看到,我们通过维护一个static 的成员变量来记录这个唯一的对象实例。通过提供一个 staitc 的接口 instance 来获得这个唯一的实例。需要说明的是, Singleton 不可以被实例化,因此我们将其构造函数声明为 protected 或者直接声明为 private。
Singleton 模式在开发中经常用到, 且不说我们开发过程中一些变量必须是唯一的,比如说打印机的实例等等。
这里写图片描述
从上面可以看出,单例模式主要有三个要点:

  1. 有一个静态成员指针指向本身
  2. 构造函数是虚拟的(因此不能实例化)
  3. 提供一个公共接口

23.4 Builder模式

23.4.1解决的问题

当我们要创建的对象很复杂的时候(通常是由很多其他的对象组合而成),我们要要复杂对象的创建过程和这个对象的表示(展示)分离开来, 这样做的好处就是通过一步步的进行复杂对象的构建, 由于在每一步的构造过程中可以引入参数,使得经过相同的步骤创建最后得到的对象的展示不一样。
还是前面的例子,对于一台车子来说,里面有许多零部件,或者许多建造步骤,
对于客户来说,其完全没有必要知道车子如何建造的,
所以我们可以通过一个建造类,实现将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示(在示例代码中可以通过传入不同的参数实现这一点)。

23.4.2 同工厂模式的区别

Builder 模式强调的是一步步创建对象,并通过相同的创建过程可以获得不同的结果对象,一般来说 Builder 模式中对象不是直接返回的。而在 AbstractFactory 模式中对象是直接返回的, AbstractFactory 模式强调的是为创建多个相互依赖的对象提供一个同一的接口。


23.5 原型模式Prototype

Prototype 模式提供了一个通过已存在对象进行新对象创建的接口( Clone), Clone()实现和具体子类实现语言相关。
这里写图片描述


23.6 桥接模式Bridge

将抽象部分与它的实现部分分离, 使得它们可以独立地变化。
比如对车(抽象类)来说,其有子类(实现)如小车、卡车、公交等,
现在我们增加特性类比如颜色(抽象类),其有子类(实现)如红色、黄色、紫色
我们可以将这两个类桥连起来(这个关联的方式,可以是类的组合)
这里说的意思不是让抽象基类与具体类分离,而是现实系统可能有多角度分类,每一种分类都有可能变化,那么把这种多角度分离出来让它们独立变化,减少它们之间的耦合性,即如果继承不能实现“开放-封闭原则”的话,就应该考虑用桥接模式。
这里写图片描述


23.7 适配器模式Adapter

Adapter 模式提供了将一个类(第三方库)的接口转化为客户(购买使用者)希望的接口。比如我们需要给我们电子产品充电,每种电子产品的充电接口并不一定相同,我需要一个适配器,使其能配合我们的电压,这个适配器里实现了不同充电接口的电压转换。
Adapter 模式实现上比较简单,要说明的是在类模式 Adapter 中,我们通过 private 继承Adaptee 获得实现继承的效果, 而通过 public 继承 Target 获得接口继承的效果。
这里写图片描述


23.8 装饰者模式Decorator

我们需要为一个已经定义好的类添加新的职责(操作), 通常的情况我们会给定义一个新类继承自定义好的类,这样会带来一个问题( 将在本模式的讨论中给出)。通过继承的方式解决这样的情况还带来了系统的复杂性,因为继承的深度会变得很深。
一种给类增加职责的方法,不是通过继承实现的,而是通过组合。有关这些内容在讨论中进一步阐述。
比如我们需要给车子装上空调,常规的方法是给车子的子类:小车、卡车、公司等每一个下面都新建一个子类(空调小车之类),
而装饰者模式只需要在车子下新建一个子类装饰者子类,然后在子类中增加空调操作。让 Decorator 直接拥有一个 ConcreteComponent 的引用(指针) 也可以达到修饰的功能, 大家再把这种方式的结构图画出来,就和 Proxy 很相似了。Proxy 模式会提供使用其作为代理的对象一样接口, 使用代理类将其操作都委托给 Proxy 直接进行。
这里写图片描述


23.9 组合模式Composite

在开发中, 我们经常可能要递归构建树状的组合结构, Composite 模式则提供了很好的解决方案。
比如,车由发动机、轮子、车座组成,而轮子又由橡胶圈和转轴等组成。这种递归结构成了组合模式。Composite 模式在实现中有一个问题就是要提供对于子节点( Leaf)的管理策略,这里使用的是 STL 中的 vector,可以提供其他的实现方式,如数组、链表、 Hash 表等。
这里写图片描述


23.10 享元模式Flyweight

Flyweight 模式中有一个类似 Factory 模式的对象构造工厂FlyweightFactory,当客户程序员( Client)需要一个对象时候就会向 FlyweightFactory 发出请求对象的消息 GetFlyweight()消息, FlyweightFactory 拥有一个管理、存储对象的“仓库”(或者叫对象池, vector 实现), GetFlyweight()消息会遍历对象池中的对象,如果已经存在则直接返回给 Client,否则创建一个新的对象返回给 Client。
例如一个字母“ a”在文档中出现了100000 次, 而实际上我们可以让这一万个字母“ a” 共享一个对象, 当然因为在不同的位置可能字母“ a” 有不同的显示效果(例如字体和大小等设置不同), 在这种情况我们可以为将对象的状态分为“外部状态”和“ 内部状态”, 将可以被共享(不会变化)的状态作为内部状态存储在对象中, 而外部对象(例如上面提到的字体、 大小等) 我们可以在适当的时候将外部对象最为参数传递给对象(例如在显示的时候,将字体、大小等信息传递给对象)。
Flyweight 模式在实现过程中主要是要为共享对象提供一个存放的“仓库”(对象池),这里是通过 C++ STL 中 Vector 容器, 当然就牵涉到 STL 编程的一些问题( Iterator 使用等)。
另外应该注意的就是对对象“仓库”(对象池) 的管理策略(查找、 插入等), 这里是通过直接的顺序遍历实现的,当然我们可以使用其他更加有效的索引策略,例如 Hash 表的管理策略,当时这些细节已经不是 Flyweight 模式本身要处理的了。
这里写图片描述


23.11 外观模式Facade

外观模式:将放置多个类的各个接口集中管理起来
Façade 模式在高层提供了一个统一的接口,解耦了系统。
这里写图片描述


23.12 代理模式Proxy

代理模式:创建开销大的对象时候,比如显示一幅大的图片,我们将这个创建的过程交给代理去完成。
为网络上的对象创建一个局部的本地代理, 比如要操作一个网络上的一个对象( 网络性能不好的时候,问题尤其突出),我们将这个操纵的过程交给一个代理去完成。
对对象进行控制访问的时候, 比如在 Jive 论坛中不同权限的用户(如管理员、 普通用户等) 将获得不同层次的操作权限, 我们将这个工作交给一个代理去完成。
远程代理,可以隐藏一个对象在不同地址空间的事实
虚拟代理:通过代理来存放需要很长时间实例化的对象
安全代理:用来控制真实对象的访问权限
智能引用:当调用真实对象时,代理处理另外一些事
这里写图片描述


23.13 模板模式Template

模板模式:在面向对象系统的分析与设计过程中经常会遇到这样一种情况:对于某一个业务逻辑(算法实现)在不同的对象中有不同的细节实现, 但是逻辑(算法) 的框架(或通用的应用算法)是相同的。 Template 提供了这种情况的一个实现框架。
Template 模式是采用继承的方式实现这一点: 将逻辑(算法) 框架放在抽象基类中, 并定义好细节的接口,子类中实现细节。由于 Template 模式的实现代码很简单,因此解释是多余的。
其关键是将通用算法(逻辑)封装起来,而将算法细节让子类实现(多态)。
唯一注意的是我们将原语操作(细节算法) 定义未保护( Protected) 成员, 只供模板方法调用(子类可以)。


23.14 策略模式Strategy

Strategy 模式和 Template 模式要解决的问题是相同 (类似)的, 都是为了给业务逻辑 (算法) 具体实现和抽象接口之间的解耦。Strategy 模式将逻辑(算法 封装到一个类(Context)里面, 通过组合的方式将具体算法的实现在组合对象中实现,再通过委托的方式将抽象接口的实现委托给组合对象实现。
Strategy 通过“组合”(委托)方式实现算法(实现 的异构,而 Template 模式则采取的是继承的方式。这两个模式的区别也是继承和组合两种实现接口重用的方式的区别
换句话说,模板模式操作的是提供具体实现的子类,而策略模式操作的是组合类(也就是存入抽象接口的类),而在被组合对象中放置接口的具体实现。


23.15 状态模式State

每个人、事物在不同的状态下会有不同表现(动作),而一个状态又会在不同的表现下转移到下一个不同的状态( State)。最简单的一个生活中的例子就是:地铁入口处,如果你放入正确的地铁票,门就会打开让你通过。在出口处也是验票,如果正确你就可以 ok,否则就不让你通过。
当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,可考虑用到状态模式。
State 模式很好地实现了对象的状态逻辑和动作实现的分离, 状态逻辑分布在 State 的派生类中实现,而动作实现则可以放在 Work类中实现(这也是为什么 State 派生类需要拥有一个指向 Context 的指针)。
这里写图片描述


23.16 观察者模式Observer

应该可以说是应用最多、影响最广的模式之一,因为 Observer 的一个实例 Model/View/Control( MVC) 结构在系统开发架构设计中有着很重要的地位和意义, MVC实现了业务逻辑和表示层的解耦。Doc/View(文档视图结构)提供了实现 MVC 的框架结构。当然, MVC 只是 Observer 模式的一个实例。
Observer 模式要解决的问题为:建立一个一(Subject)对多(Observer)的依赖关系, 并且做到当“一”变化的时候, 依赖这个“一”的多也能够同步改变(通过Vector)。 这里的目标 Subject 提供依赖于它的观察者 Observer 的注册(Attach) 和注销(Detach)操作,并且提供了使得依赖于它的所有观察者同步的操作(Notify)。 观察者 Observer 则提供一个 Update 操作, 注意这里的 Observer 的 Update 操作并不在 Observer 改变了 Subject 目标状态的时候就对自己进行更新, 这个更新操作要延迟到 Subject 对象发出 Notify 通知所有Observer 进行修改(调用 Update)。
定义了一种一对多的关系,让多个观察对象(公司员工)同时监听一个主题对象(秘书) ,主题对象状态发生变化时,会通知所有的观察者,使它们能够更新自己。


23.17 备忘录模式Memento

有时在进行软件系统的设计时,需要有一些撤销操作,这是由 Memento 模式提供的。Memento 模式的关键就是要在不破坏封装行的前提下,捕获并保存一个类的内部状态, 这样就可以利用该保存的状态实施恢复操作。
下面是一个Memento 模式的典型代码。Originator 为 friend 类,可以访问内部信息,但是其他类不能访问。
Memento 模式的关键就是 friend class Originator;我们可以看到, Memento 的接口都声明为 private,而将 Originator 声明为 Memento 的友元类。我们将 Originator 的状态保存在Memento 类中,而将 Memento 接口 private 起来, 也就达到了封装的功效。
在 Originator 类中我们提供了方法让用户后悔:
RestoreToMemento(Memento* mt);
我们可以通过这个接口让用户后悔。在测试程序中,我们演示了这一点: Originator 的状态由 old 变为 new 最后又回到了 old。
这里写图片描述


23.18 中介者模式Mediator

在面向对象系统的设计和开发过程中, 对象之间的交互和通信是最为常见的情况, 因为对象间的交互本身就是一种通信。
在系统比较小的时候,对象间的通信不是很多,对象也比较少,可以直接硬编码到各个对象的方法中。
但是当系统规模变大, 对象间的通信也变得越来越复杂, 需要提供一个专门处理对象间交互和通信的类,这就是中介者模式。
中介者模式提供将对象间的交互和通讯封装在一个类中, 各个对象间的通信不必显势去声明和引用, 大大降低了系统的复杂性能。另外 还带来了系统对象间的松耦合。
中介者模式是一种很有用并且很常用的模式,它通过将对象间的通信封装到一个类中, 将多对多的通信转化为一对多的通信, 降低了系统的复杂性。 通过中介者,各个通信对象就不必维护各自通信的对象和通信协议,降低了系统的耦合性,中介者和各个 通信对象就可以相互独立地修改了。
中介者模式还有一个很显著额特点就是将控制集中, 集中的优点就是便于管理, 也正式符合了面向对象设计中的每个类的职责要单一和集中的原则。


23.19 命令模式Command

命令模式把请求一个操作的对象与知道怎么操行一个操作的对象分开。
命令模式在实现的实现和思想都很简单,其关键就是将一个请求封装到一个命令类中, 再提供处理对象,最后命令由处理者激活。 另外, 我们可以将请求接收者的处理抽象出来作为参数传给命令对象。
这里写图片描述


23.20 访问者模式Visitor

在面向对象系统的开发和设计过程,经常会遇到一种情况就是需求变更。最常见的解决方案就是给已经设计好的类添加新的方法去实现客户新的需求,但不停地打补丁, 其带来的后果就是设计根本就不可能封闭、编译永远都是整个系统代码。
Visitor 模式则提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。„适用于数据结构稳定的系统。它把数据结构和作用于数据结构上的操作分离开,使得操作集合。优点:新增加操作很容易,因为增加新操作就相当于增加一个访问者,访问者模式将有关的行为集中到一个访问者对象中。
这里我们可以向 Element 类仅仅提供一个接口 Visit(),而在 Accept()实现中具体调用哪一个 Visit()操作则通过函数重载的方式实现。
要求 Visitor 可以从外部修改 Element 对象的状态, 这一般通过两个方式来实现:

  • Element 提供足够的 public 接口, 使得 Visitor 可以通过调用这些接口达到修改 Element 状态的目的;
  • Element 暴露更多的细节给 Visitor,或者让 Element 提供 public 的实现给 Visitor,或者将 Visitor 声明为 Element 的友元类, 仅将细节暴露给 Visitor。

但是无论那种情况,特别是后者都将是破坏了封装性原则。


23.21 负责链模式Chain of Responsibility

模式描述其实就是这样一类问题将可能处理一个请求的对象链接成一个链, 并将请求在这个链上传递, 直到有对象处理该请求(可能需要提供一个默认处理所有请求的类,例如 MFC 中的 CwinApp 类)。
这里写图片描述


23.22 迭代器模式Iterator

应该是最为熟悉的模式了,最简单的证明就是我在实现 Composite 模式、Flyweight 模式、 Observer 模式中就直接用到了 STL 提供的 Iterator 来遍历 Vector 或者 List数据结构。
Iterator 模式也正是用来解决对一个聚合对象的遍历问题, 将对聚合的遍历封装到一个类中进行,这样就避免了暴露这个聚合对象的内部表示的可能。


23.23 解释器模式Interpreter

一些应用提供了内建的脚本或宏语言,可以用于自定义操作命令。 Interpreter 模式的目的就是生成一个提供自定义语言语法表示的解释器,然后通过这个解释器来解释语言中的句子。
比如HTML格式或XML 格式的数据解析。Interpreter 模式则提供了一种很好的组织和设计这种解析器的架构。
Interpreter 模式中使用类来表示文法规则,可以很容易实现文法的扩展。另外可以使用 Flyweight 模式来实现终结符的共享。

这篇博文是个人的学习笔记,内容许多来源于网络(包括CSDN、博客园及百度百科等),博主主要做了微不足道的整理工作。由于在做笔记的时候没有注明来源,所以如果有作者看到上述文字中有自己的原创内容,请私信本人修改或注明来源,非常感谢>_<

0 0
原创粉丝点击