设计模式代码样例:Git
子类必须能够替换它们的基类(IS-A)
继承表达类型抽象
使用封装来创建对象的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合
“框架与应用程序的划分":组件协作”模式通过晚期绑定来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
定义:定义一个操作的算法的骨架(稳定),而将某些步骤延迟(变化)到子类中。Template Method使得子类可以不改变(复用)算法的结构,即可重定义(重写)某些特定的步骤。
应用场景:在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因 (比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用 你”的反向控制结构是Template Method的典型应用。
定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
客户程序(稳定)
应用场景: 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
总结:
定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生变化时,所以依赖于它的对象都能得到通知并自动更新。
适用场景: 需要为某些对象建立一种“通知依赖关系” —— 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
动态(组合形式)给一个对象增加额外的职责。就增加功能而言,Decorator模式相比生成子类(继承形式)更为灵活。(消除重复代码和减少子类个数)
解决场景: 过度地使用继承来扩展对象的功能”, 由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。如下图所示例子(Stream对应Component,Concrete ComponentA-C对应File/Network/Memory Stream):
继承形式:
组合形式:
将抽象部分与实现部分分离,使它们可以独立的变化。
通过“对象创建”模式绕开new,来避免对象创建( new )过程中所导致的紧耦合( 依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
定义一个用于创建对象的接口,将子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
应用场景: 创建对象的工作由于需求的变化, 需要创建的对象的具体类型经常变化。提供一 种“封装机制”来避免客户程序和这种“具体对象创建工作“的紧耦合。
提供一个接口,负责创建一系列“相关或者相互依赖的对象”,而无需指定它们的具体类。
应用场景: 一系列相互依赖的对象”的创建工作由于需求的变化,往往存在更多系列对象的创建工作。提供一 种“封装机制”来避免客户程序和这种“多系列具体对象创建工作” 的紧耦合。
与工厂模式的创建单一对象不同,抽象工厂模式是应对“一系列对象”的创建。
使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。
将一个复杂对象的构建和其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)
应用场景: 面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在 一起的算法却相对稳定。
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
应用场景: 存在这样一些特殊的类,必须保证他们在系统只存在一个实例,才能确保他们的逻辑正确性和效率。
运用共享技术有效地支持大量的细力度对象。
采用对象共享的方法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。
在组件构建过程中,某些接口之间的依赖常常会带来许多问题、甚至根本无法实现。采用添加一层间接接口,来隔离本来互相紧密关联的接口是一种常见的解决方案。
为子系统中的一组接口提供一个一致(稳定)的界面,Facade模式定义一个高层接口,这个接口使得子系统更加容易使用(复用)
应用场景: 组件的客户和组件中的子系统有了过多耦合,门面模式可以将客户程序的演化和内部子系统的变化之间的依赖进行解耦。
为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的对象。
应用场景: 有些对象某种原因(比如对象创建的开销很大,或者某些操作系统需要安全控制,或者需要进程外的访问等),直接访问对象会带来很多麻烦,甚至根本无法直接访问,增加一层间接层(Proxy)可以不失去透明操作对象的同时来管理/控制这些对象特有的复杂性。比如grpc的客户端,只能通过rpc来获取服务端的对象。
将一个类的接口转换成客户希望的另一个接口。Adapter使得那些原本由于接口不兼容而不能一起工作的类可以一起工作。
应用场景: 在软件系统中,由于应用环境的变化,需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。适配器模式既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口。
用一个中介对象来封装一系列的对象交互。中介者使得对象之间不需要显示的相互引用(编译时依赖->运行时依赖),从而使得其耦合松散,可以独立得改变它们之间的交互。
应用场景: 在软件构建过程中,多个对象互相关联交互的情况对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。在这种情况下,可以使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。
在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理,同时又维持高层模块的稳定?
允许一个对象在其内部状态改变时改变它的行为,从而使对象看起来似乎修改了其行为。
应用场景: 某些对象的状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。状态模式可以在运行时根据对象的状态来透明地更改对象的行为,而不会为对象操作和状态转化之间引入紧耦合。
在不破坏封装性的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可以将对象恢复到原先保存的状态。
应用场景: 某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。备忘录模式可以实现对象状态的良好保存与恢复,但同时又不会因此而破坏对象本身的封装性。
一些组件内部有特定的数据结构,如果让客户程序依赖这些数据结构,将极大的破坏组件的复用性。这时候,将这些特定的数据结构封装在内部,对外提供统一的接口,来实现与特定数据结构无关的访问。
(解耦客户程序和组件内特定的数据结构)
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
应用场景: 客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。组合模式将“客户代码与复杂的对象容器结构”解耦,让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象样来处理复杂的对象容器。
提供一种方法顺序访问聚合对象中的各个元素,而又不暴露该对象的内部表示。
C++标准库中的迭代器是基于模板实现,能够实现编译时多态,比面向对象的运行时多态性能更好,因此在C++中并不推荐迭代器模式。
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连城一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
应用场景: 一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显式指定,将必不可少地带来请求发送者与接受者的紧耦合。职责链模式可以使请求的发送者不需要指定具体的接受者,让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。
在组件的构建过程中,组件行为的变化经常导致组件本身剧烈的变化。“行为变化”模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合。
将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化。对请求排队或者记录请求日志,以及支持可撤销的操作。
(Invoker作为调用者,请求执行某个Command,然后Command传递到Receiver,即真正执行命令的对象)
应用场景:“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合比如需要对行为进行“记录、撤销/重做(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。命令模式可以将“行为请求者”与“行为实现者解耦,将一组行为抽象为对象,可以实现二者之间的松耦合。
表示作用于某对象结构中的各元素的操作。使得可以在不改变各元素的类的前提下,定义作用于这些元素的新操作。
应用场景:由于需求的改变,某些类层次结构中常常需要增加新的行为(方法 ),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担。访问者模式可以在不更改类层次结构的前提下在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作。
在特定领域中,某些变化虽然频繁,但可以抽象为某种规则。这时候,结合特定领域,将问题抽象为语法规则,从而给出在该领域下的一般性解决方案。
给定一个语言,定义它的文法的一种表示,并定义一种解释器,这种解释器使用该表示来解释语言中的句子。
代码样例:Git