Boolan(博览网)——C++ 设计模式(第十二周)

目录

1. 工厂方法(Factory Method)

2. 抽象工厂(Abstract Factory)

3. 原型模式(Prototype)

4. 构建器(Builder)

5. 门面模式(Facade)

6. 代理模式(Proxy)

7. 适配器(Adapter)

8. 中介者(Mediator)


「对象创建」模式

  • 通过「对象创建」模式绕开 new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。

  • 典型模式

    • Factory Method(工厂方法)
    • Abstract Factory(抽象工厂)
    • Prototype(原型模式)
    • Builder(构建器)

1. 工厂方法(Factory Method)

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
——《设计模式》GoF

动机

  • 在软件系统中,经常会面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。

  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种「封装机制」来避免客户程序和这种「具体对象创建工作」的紧耦合?

结构

Boolan(博览网)——C++ 设计模式(第十二周)_第1张图片
Boolan(博览网)——C++ 设计模式(第十二周)_第2张图片

笔记

  • 工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来运行。你想要加功能,本来是改工厂类的,而现在是修改客户端。

要点总结

  • Factory Method 模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。

  • Factory Method 模式通过面向对象的手法,将所要创建的对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。

  • Factory Method 模式解决「单个对象」的需求变化。缺点在于要求创建方法/参数相同。

2. 抽象工厂(Abstract Factory)

提供一个接口,让该接口负责创建一系列「相关或相互依赖的对象」,无需指定他们具体的类。
——《设计模式》GoF

动机

  • 在软件系统中,经常会面临着「一系列相互依赖的对象」的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。

  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种「封装机制」来避免客户程序和这种「多系列具体对象创建工作」的紧耦合?

结构

Boolan(博览网)——C++ 设计模式(第十二周)_第3张图片
Boolan(博览网)——C++ 设计模式(第十二周)_第4张图片

笔记

  • 抽象工厂的最大的好处是易于交换产品系列,由于具体工厂类,在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。

  • 第二大好处是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码上。

要点总结

  • 如果没有对应“多系列对象构建”的需求变化,则没有必要使用 Abstract Factory 模式,这时候使用简单的工厂完全可以。

  • “系列对象”指的是在某一特定系列下的对象之间有相互依赖或作用的关系。不同系列的对象之间不能相互依赖。

  • Abstract Factory 模式主要在于应对“新系列”的需求变动,其缺点在于难以应对“新对象”的需求变动。

3. 原型模式(Prototype)

使用原型实例指定创建对象的种类,然后通过拷贝(深拷贝)这些原型来创建新的对象。
——《设计模式》GoF

动机

  • 在软件系统中,经常面临着“某些结构复杂的对象”(简单的 new 达不到需要的效果)的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有着比较稳定一致的接口。

  • 如何应对这种变化?如何向「客户程序(使用这些对象的程序)」隔离出「这些易变对象」,从而使得「依赖这些易变对象的客户程序」不随着需求改变而改变?

结构

Boolan(博览网)——C++ 设计模式(第十二周)_第5张图片
Boolan(博览网)——C++ 设计模式(第十二周)_第6张图片

笔记

  • 原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节。

  • 一般在初始化的信息不发生变化的情况下,克隆是最好的办法。这既隐藏了对象创建的细节,又对性能是大大的提高。

  • 原型模式等于是不用重新初始化对象,而是动态地获得对象运行时的状态。

  • 问:什么时候使用原型模式?什么时候使用工厂方法?
    答:当创建对象是非常简单的几个步骤时,用工厂方法;当创建对象时有非常复杂的中间状态,且希望保留这个中间状态时,用原型模式。

要点总结

  • Prototype 模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,他同样要求这些“易变类”拥有“稳定的接口”

  • Prototype 模式对于“如何创建易变类的实体对象”采用“原型克隆”的方法来做,它使得我们可以非常灵活地动态创建「拥有某些稳定接口」的新对象——所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方 Clone。

  • Prototype 模式中的 Clone 方法可以利用某些框架中的序列化来实现深拷贝。(C++ 中使用拷贝构造函数就能很好的实现)

4. 构建器(Builder)

将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。
——《设计模式》GoF

动机

  • 在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将他们组合在一起的算法却相对稳定

  • 如何应对这种变化?如何提供一种「封装机制」来隔离出「复杂对象的各个部分」的变化,从而保持系统中的「稳定构建算法」不随着需求改变而改变?

结构

Boolan(博览网)——C++ 设计模式(第十二周)_第7张图片
Boolan(博览网)——C++ 设计模式(第十二周)_第8张图片

笔记

  • 在 C++ 中,在构造函数里面,调用虚函数是调用父类的虚函数而不是子类的虚函数(静态绑定)。

  • 使用构建器模式,用户只需指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需知道了。

  • 构建器模式的好处就是使得建造代码与表示代码分离,由于建造者隐藏了该产品是如何组装的,所以若需要改变一个产品的内部表示,只需要再定义一个具体的建造者就可以了。

  • 构建器模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式。

要点总结

  • Builder 模式主要用于“分步骤构建一个复杂对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化

  • 变化点在哪里,封装哪里——Builder 模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。

  • 在 Builder 模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#)。(静态绑定 vs. 动态绑定)


「接口隔离」模式

  • 在组件构建过程中,某些接口之间直接的依赖常常会带来很多问题,甚至根本无法实现。采用添加一层间接(稳定)接口,来隔离本来相互紧密关联的接口是一种常见的解决方案。

  • 典型模式

    • Facade(门面模式)
    • Proxy(代理模式)
    • Adapter(适配器)
    • Mediator(中介者)

5. 门面模式(Facade)

Boolan(博览网)——C++ 设计模式(第十二周)_第9张图片

为子系统中的一组接口提供一个一致(稳定)的界面,Facade 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。
——《设计模式》GoF

结构

Boolan(博览网)——C++ 设计模式(第十二周)_第10张图片

动机

  • 上述方案 A 的问题在于组件的客户和组件中的各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。

  • 如何简化外部客户程序和系统间的交互接口?如何将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦?

笔记

  • 首先,在设计初期阶段,应该要有意识的将不同的两个层分离,层与层之间建立 Facade。

  • 其次,在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加
    Facade 可以提供一个简单的接口,减少它们之间的依赖。

  • 第三,在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了。为新系统开发一个 Facade 类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与 Facade 对象交互(Facade 与遗留代码交互所有复杂的工作)。

要点总结

  • 从客户程序的角度来看,Facade 模式简化了整个组件系统的接口,对于组件内部与外部客户程序来说,达到了一种“解耦”的效果——内部子系统的任何变化不会影响到 Facade 接口的变化。

  • Facade 设计模式更注重架构的层次去看整个系统,而不是单个类的层次。Facade 很多时候更是一种架构设计模式。

  • Facade 设计模式并非一个集装箱,可以任意地放进任何多个对象。Facade 模式中组件的内部应该是“相互耦合关系比较大的一系列组件”,而不是简单的功能集合,以便能够实现松耦合,高内聚的特性。

6. 代理模式(Proxy)

动机

  • 在面向对象系统中,有些对象由于某种原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者或者系统结构带来很多麻烦。

  • 如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式。

为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
——《设计模式》GoF

结构

Boolan(博览网)——C++ 设计模式(第十二周)_第11张图片

笔记

  • 远程代理:也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。

  • 虚拟代理:根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。

  • 安全代理:用来控制真实对象访问时的权限。

  • 智能指引:当调用真实的对象时,代理处理另外一些事。

要点总结

  • “增加一层间接层”是软件系统中对许多复杂问题的一种常见解决方案。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的 Proxy 对象便是解决这一问题的常用手段。

  • 具体 Proxy 设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如 copy-on-write 技术,有些可能对组建模块提供抽象代理层,在架构层次对对象做 proxy。

  • Proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。

7. 适配器(Adapter)

将一个类的接口转换成客户希望的另一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
——《设计模式》GoF

动机

  • 在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。

  • 如何应对这种「迁移的变化」?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?

结构

Boolan(博览网)——C++ 设计模式(第十二周)_第12张图片

笔记

  • 系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。

  • 在 GoF 的设计模式中,讲了两种适配器:类适配器和对象适配器,由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而 C#、VB.NET、JAVA 等语言都不支持多重继承(C++ 支持),也就是一个类只有一个父类,所以不推荐使用。

  • 在双方都不太容易修改的时候再使用适配器模式来适配,否则应该考虑用重构统一接口。

要点总结

  • Adapter 模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。

  • GoF 23 定义了两种 Adapter 模式的结构实现:对象适配器和类适配器。但类适配器采用“多继承”的实现方式,一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神。

  • Adapter 模式可以实现的非常灵活,不必拘泥于 GoF23 中定义的两种结构。例如,完全可以将 Adapter 模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。

8. 中介者(Mediator)

用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各个对象不需要显示的相互引用(编译时依赖 -> 运行时依赖),从而使其耦合松散(管理变化),而且可以独立的改变他们之间的交互。
——《设计模式》GoF

动机

  • 在软件构建过程中,经常会出现多个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到了一些需求的更改,这种直接的引用关系将面临不断的变化。

  • 在这种情况下,我们可以使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。

结构

Boolan(博览网)——C++ 设计模式(第十二周)_第13张图片
Boolan(博览网)——C++ 设计模式(第十二周)_第14张图片

笔记

  • 尽管将一个系统分割成许多对象通常可以增加其可复用性,但是对象间相互连接的激增又会降低其可复用性了。
    大量的连接使得一个对象不可能在没有其他对象的支持下工作,系统表现为一个不可分割的整体,所以,对系统的行为进行任何较大的改动就十分困难了。

  • 中介者模式很容易在系统中应用,也很容易在系统中误用。当系统出现了“多对多”交互复杂的对象群时,不要急于使用中介者模式,而要反思你的系统在设计上是不是合理。

  • 中介者模式的优点:

    1. Mediator 的出现减少了各个 Colleague 的耦合,使得可以独立地改变和复用各个 Colleague 类和 Mediator。
    2. 由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统。
  • 中介者模式的缺点:
    由于 ConcreteMediator 控制了集中化,于是就把交互复杂性变为了中介者的复杂性,这就使得中介者变得比任何一个 ConcreteColleague 都复杂。

  • 中介者模式一般应用于一组对象以定义良好但是复杂的方式进行通信的场合,以及想定制一个分布在多个类中的行为,而又不想生成太多的子类的场合。

要点总结

  • 将多个对象间复杂的关联关系解耦,Mediator 模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关联”为“多个对象和一个中介者关联”,简化了系统的维护,抵御了可能的变化。

  • 随着控制逻辑的复杂化,Mediator 具体对象的实现可能相当复杂。这时候可以对
    Mediator 对象进行分解处理。

  • Facade 模式是解耦系统间(单向)的对象关联关系;Mediator 模式是解耦系统内各个对象之间(双向)的关联关系。

你可能感兴趣的:(Boolan(博览网)——C++ 设计模式(第十二周))