设计模式(一)
在极端变化和极端稳定中寻找变化点,并分离它们,在变化点应用设计模式
好的设计模式就是可以满足 应对变化,提高复用
重构获得设计模式是使用设计模式最好的方法,而不是一步到位
1. 组件协作模式
通过晚绑定,实现框架与应用程序间的松耦合,是二者之间协作时常用的模式
Template Method
动机:在软件构建过程中,某一项任务常有稳定的整体操作,但子步骤却有很多改变的需求,或者由于固有的原因(比如框架和应用之间的关系)而无法和任务的整体结构同时实现。
- 一种非常基础的设计模式,利用虚函数机制使程序扩展更加灵活,代码复用的基本实现结构
- 除了灵活应对子步骤变化,“不要调用父类,让父类调用子类”的反向控制结构是典型应用
- 被模板设计调用的虚方法可以有实现,也可以没有实现,一般把它们设为protected方法
Strategy
动机:在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂,而且有时支持不使用的算法也是性能负担。将算法与对象解耦,进行避免。
定义一系列算法,把它们封装起来,并使他们可相互替换(变化),该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展)。
- Strategy及其子类为组件提供一系列可重用的算法,使得类型在运行时方便的在算法中切换
- 消除条件判断语句,就是在解耦合,含有很多条件判断的代码可能需要Strategy模式
- 如果Strategy对象没有实例变量,那么可以上下文共享一个Strategy对象,从而节省对象开销
Observer / Event
动机:构建时,需要为某些对象建立一种通知依赖关系——一个对象(目标对象)的状态发生变化时,所有的依赖对象(观察者对象)都将得到通知。如果这种依赖过于紧密,软件就不能很好的抵御变化。为了使依赖弱化,形成稳定依赖,实现松耦合。
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(subject)的状态发生变化时,所有依赖它的对象都得到通知并自动更新。
- 使用面向对象的抽象,Observer模式使我们可以独立的改变目标和观察者,从而使两者间依赖关系得到松耦合
- 目标发送通知时,无需指定观察者,通知(可携带通知信息作为参数)会自动传播
- 观察者自己决定是否订阅通知,目标对象对此一无所知
- Observer模式是基于事件的UI框架中常用的模式,也是MVC模式重要的组成部分
2. 单一职责模式
Decorator
动机:在某些情况下我们可能会过度使用继承来扩展对象的功能,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;同时随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。为了使对象功能的扩展能根据需要动态实现。
动态(组合)的给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)
- 通过采用组合而非继承,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免使用继承造成的灵活性差和多子类衍生问题
- Decorator类在接口上表现为is-a Component的继承关系,在实现上又有has-a Component的组合关系。
- 在于解决“主题类在多个方向上的扩展功能”,而非解决“多子类衍生的多继承”问题。
Bridge
动机:由于某些类的固有的实现逻辑,使得他们具有两个,甚至多个变化的维度。利用面向对象来轻松沿着两个乃至多个方向变化。
将抽象部分(业务功能)与实现部分(平台实现)分离,使他们可以独立的变化
- Bridge模式使用“对象间组合关系”解耦了抽象和实现间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。即子类化它们。
- Bridge模式有时候类似于多继承方案,但多继承方案往往违背单一职责原则,复用性比较差,所以Bridge模式比多继承更好的解决方法。
- Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式。
3. 对象创建模式
通过对象创建绕开 new, 来避免对象创建过程中导致的紧耦合(依赖具体类),从而支持对象创建的稳定。是接口抽象之后的第一步工作。
Factory method
动机:在软件系统中,经常面临创建对象的工作,由于需求的变化,需要创建的对象的具体类型经常变化。需要提供一种封装机制来避免客户程序和这种具体对象创建工作的紧耦合。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化(目的:解耦;手段:虚函数)延迟到子类。
- 用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
- 通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好的解决了这种紧耦合关系。
- 解决单个对象的需求变化,缺点在于要求创建方法/参数相同。
Abstract factory
动机:在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。需要绕过常规的创建方法(new),提供一种封装机制来避免客户程序和这种“多系列具体对象创建工作”的紧耦合。
提供一个接口,让接口负责创建一系列“相关或者相互依赖的对象”,无需指定他们具体的类。
- 如果没有“多系列对象构建”的需求变化,则没有必要使用抽象工厂模式,简单工厂就完全可以了。
- 系列对象指的是在某一特定系列下的对象之间有相互依赖,或作用的关系。不同系列的对象之间不能相互依赖。
- 主要应用新系列的需求变动,缺点在于不好应对新对象的需求变动。
prototype原型模式
动机:在软件系统中,经常面临 “某些结构复杂的对象”的创建工作,由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较一致的接口。隔离出这些异变对象,从而使得依赖这些易变对象的客户程序不随着需求改变而改变。
使用原型实例指定创建对象的种类,然后通过拷贝(深克隆)这些原型来创建新的对象。
- 同样用于隔离类对象的使用者和具体类型之间的耦合关系,他同样要求这些“易变类”拥有稳定的接口。
- 原型模式对于“如何创建易变类的实体对象”采用“原型克隆”的方法,使得我们可以非常灵活的动态创建“拥有某些稳定接口”的新对象——所需的工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方clone
- clone方法可以利用某些框架中的序列化来实现深拷贝。
Builder
动机:在软件中,有时候面临“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将他们组合在一起的算法却相对稳定。提供一种封装机制来隔离出复杂对象的各个部分的变化。
将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。
- 主要用于分步骤构建一个复杂的对象。在这其中分步骤是一个稳定的算法,而复杂对象的各个部分则经常变化。
- 变化点在哪,封装哪里——Builder模式主要在于应对复杂对象各个部分的频繁需求变动,其缺点在于难以应对分步骤构建算法的需求变动。
- 注意不同语言中构造器内调用虚函数的差别(c++ vs. Java)。