对设计模式的认识(一)
四人帮的那本书并没有讲到并发或者分布式或者实时程序的设计模式。
设计模式是把过去成功的并且被重复使用的设计经验以人们能够有效利用的方式记录下来。
一个设计模式有四个基本要素:
1. 模式名称:用于记住该模式并且可以和他人进行交流。
2. 问题:该模式用于解决的问题,也即该模式应用的领域。
3. 解决方案:对该模式的具体描述。
4. 效果:该模式应用的效果及影响,以及使用该模式需要权衡的问题。
设计模式可以按照目的和范围进行分类。
以下为设计模式分类图:
|
创建型 |
结构型 |
行为型 |
类 |
Factory Method |
Adapter |
Interpreter Template Method |
对象 |
Abstract Factory Builder Prototype Singleton |
Adapter Bridge Composite Decorator Facade Flyweight Proxy |
Chain of Responsibility Command Iterator Mediator Memento Observer State Strategy Visitor |
其中创建型、结构型、行为型是按设计模式的目的进行分类;类和对象是按设计模式的范围进行分类。
创建型模式用于处理对象的创建;结构型模式用于处理类或对象的组合;行为型模式用于处理类或对象的交互和职责分配。
类模式用于处理类之间的各种设计问题,而对象模式则用于处理对象之间的设计问题。
请求是使对象进行操作的唯一方法,操作又是改变对象内部数据的唯一方法。
对象的接口是对象所有操作型构(signature)的集合。
类型是标示特定接口的一个名字,一个对象如果实现了某个接口,那它就具有该接口的类型。一个对象可以有多个类型,而一个类型可以被多个对象具有。当一个类型的接口继承另一个类型的接口时,它就是另一个类型的子类型,而另一个类型是它的超类型。
对象的接口与其功能实现是分离的,两个接口相同的对象可以有完全不同的实现。正是这一特性使得动态绑定成为可能,基于动态绑定也就产生了面向对象的一大特征---多态。
设计模式通过确定接口的组成部分和经接口发送的数据类型,来帮助定义接口。
对象的类已经具体实现了该对象的功能,而对象的类型只是定义了该对象需要实现的操作,只是一个操作的框架而并没有任何具体的功能实现,因而类与接口的主要区别就在于此,类继承是把实现一起继承了,而接口继承只是继承一个框架,至于怎么做则由实现该接口的类来完成。
应该面向接口编程,这样可以增加灵活性,扩展性并且易于维护。
系统功能的复用可以通过类的继承和对象的组合来完成,类的继承由于子类对父类实现细节大部分可见,所以也称为白箱复用,而对象组合则由于对象之间的实现细节不可见而称为黑箱复用。
类的继承缺乏灵活性,由于父子类的耦合性很紧,所以不易于未来的扩展和维护,可以通过继承实现较少细节的抽象类来尽量解决这一问题。
在设计时应该优先使用对象组合,其次才是类的继承,并且要保证类的继承不至于过于庞大而难以维护。
委托是一种组合方法,它使组合具有复用能力。
模板技术其实也是一种复用技术,概括地讲它是一种参数化类型的实现,即未指定的类型通过参数来提供具体类型。
不过对象组合会造成一定程度的低效,因而需要综合考虑系统需求来决定如何使用这些复用技术。
对象之间的组合可以是一个对象是另一个对象的一部分,也可以是两个对象互相独立但会交互,具体如何使用视需求而定。
聚合就是一个对象是另一个对象的一部分。
设计时应该尽量考虑将来的变化,即使只是一个方向也是必要的。系统组件之间应该独立从而使将来某个组件的变化对其它组件的影响降到最小。
对于容易发生变化的组件或者功能必须进行封装和独立以确保将来的变化不会影响其它组件和功能。
引起重新设计的原因:
1、显示地指定一个类来创建对象
2、对特殊操作的依赖
3、对硬件和软件平台的依赖
4、对对象表示和实现的依赖
5、对算法的依赖
6、紧耦合
7、通过生成子类来扩展功能
8、无法方便地对类进行修改
选择设计模式的方法:
1、需要解决的设计问题
2、设计模式的意图
3、模式之间的联系
4、相似目的的模式
5、避免重新设计
6、找出易变的部分
设计模式虽然提供了灵活性、可扩展性和可维护性,但是会使系统效率降低,结构变得复杂,因而使用某种设计模式时需要考虑它的限制以保证事倍功半。
各个模式的概括:
1、Abstract Factory:提供一个创建一系列相关或者相互依赖的对象的接口,但无需指定它们具体的类。
2、Adapter:将一个类的接口转换成客户希望的另一个接口,从而使原本因接口不兼容无法一起工作的类可以一起工作。
3、Bridge:将抽象部分和实现部分分离,从而使它们可以独立变化。
4、Builder:将一个复杂的对象的构建和它的表示分离,从而使相同的构建过程可以创建不同的表示。
5、Chain of Responsibility:为了解除请求者和接收者的耦合,使多个对象都有机会处理该请求,将多个对象串成一条链,使请求通过链上的每个对象,各对象根据自己的需求来处理请求。
6、Command:将请求封装成对象,从而可以用不同的请求对客户进行参数化,并可以使请求排队,记录请求日志以及取消请求。
7、Composite:将对象组合成树形结构以表示“整体-部分”的层次结构,从而使客户对于单个对象和组合对象的使用具有一致性。
8、Decorator:动态地给对象添加额外的职责,比生成子类更加灵活。
9、Facade:为子系统中的一组接口提供一个一致的界面(即一个高层接口),从而使子系统更加容易使用。
10、Factory Method:定义了一个创建对象的接口,由子类决定实例化哪个类,使一个类的实例化延迟到其子类。
11、Flyweight:运用共享技术有效地支持大量的细粒度对象。
12、Interpreter:给定一个语言及它的文法,并创建一个解释器,该解释器使用该文法来解释该语言的句子。
13、Iterator:提供一种方法可以顺序访问一个聚合对象中的各个元素,而无需暴露该对象的内部表示。
14、Mediator:封装各个对象的交互,从而使各个对象无需通过互相引用来交互,降低了各个对象的耦合度,并且可以独立地改变对象之间的交互而无需影响对象。
15、Memento:在不破坏封装性的前提下,可以获取一个对象的内部状态,并且把这个状态保存在对象之外,从而可以将对象恢复至这个状态。
16、Observer:定义对象之间一种一对多的依赖关系,以便于在对象发生变化时,可以及时通知到依赖于它的其他对象并使它们发生相应变化。
17、Prototype:用原型实例来指定对象的类型,并且通过拷贝这个原型实例来创建对象。
18、Proxy:为某个被访问对象提供代理,以控制所有对它的访问。
19、Singleton:保证一个类只有一个实例,并提供一个它实例的全局访问点。
20、State:允许一个对象在其内部状态发生变化时改变它的行为,这使对象看起来似乎修改了它所属的类。
21、Strategy:封装算法,从而可以使算法独立变化而不影响其它对象。
22、Template Method:定义算法的骨架,将一些步骤的实现交给子类解决。
23、Visitor:定义作用于对象中各元素的操作,可以在不改变各元素的类的前提下,定义作用于这些元素的新操作。
各模式的具体含义:
1、Abstract Factory
有一个抽象工厂基类,它定义了生产哪些类型产品,然后每个类型产品都有一个基类,之后不同类型产品的基类派生出同类型的不同产品,而抽象工厂基类派生出生产具体产品的具体工厂(工厂生产的产品类型都一样,但每个类型的产品不一样),结构如图:
2、Builder
一个类型的产品可以是多样的,有多种构造方式,但是产品可以划分归纳出来的组成部分是一样的,不同的是通过这些组成部分来构造产品时所使用的组成部分次数及组成部分生成的方法不同,这个时候就可以通过Builder模式来完成构建产品。首先,定义一个抽象类作为基类,规定好组成部分及返回构造好的产品,之后,派生出具体的类通过不同的构造方法来构造相同的组成部分,完成后返回构造好的产品。最后,得有一个创建并操作Builder类的对象,称为Director,它可以控制先构造哪个组成部分,这个组成部分要构造多少次,即通过不同的构造方法来构造不同的产品,它是产品的终极构造者,具体结构如图:
3、Factory Method
一个抽象工厂和一个抽象产品,工厂定义了一个创建产品的方法,以及一个利用创建后的产品做一些操作的方法,其中创建产品这个方法并没有创建具体的产品,而是把它留给派生出的具体工厂来实现,具体工厂根据需求创建派生于抽象产品的具体产品,具体结构如图:
4、ProtoType
一个产品需要复制自己,这个时候就可以用ProtoType模式,具体结构如图:
5、Singleton
当全局只需要一个对象实例时,就可以使用Singleton模式,具体结构如图:
6、Adapter
分为两种,类Adapter和对象Adapter。类Adapter就是通过多重继承来实现Adapter,对象Adapter则通过继承一个类,其他用对象组合来实现Adapter。具体结构如图:
7、Bridge
把一个系统分为独立的两部分(可以是抽象和具体),其中一部分通过调用另一部分来实现操作,但是由于独立了这两个部分,所以它们内部发生变化时不会影响到另一部分。具体结构如图:
8、Composite
子类分为叶结点和包含父类一组对象的组合节点,以此类推,形成一个树状结构。具体结构如图:
9、Decorator
Decorator基类和所要修饰的子类继承自同一个父类,并且它包含了父类的一个对象,在实现父类定义的操作时,它会先执行父类或者相应子类的操作,然后加上自己的操作来完成装饰功能,它根据不同的需求可以派生出自己的子类,从而使自己的装饰功能变得空前强大。具体结构如图:
10、Facade
在子系统外面包一层,从而使子系统能够方便地与其他系统交互。具体结构如图:
11、Flyweight
将系统中可以共享的内部状态整理成为数不多的Flyweight对象,同时通过Flyweight对象的操作方法来处理外部状态并保存在客户系统那里,另外用一个Flyweight工厂来创建并保存Flyweight对象。这样可以大幅降低对象的数量,从而大幅节约开销。具体结构如图:
12、Proxy
和被代理的目标对象继承自同一个父类(便于客户系统操作,接口一致使代理对象看上去就和目标对象一样),并且包含被代理的目标对象,根据需求的不同增加了一些操作并限制了对目标对象的调用,比如安全性,数据完整性等,从而确保目标对象的调用符合需求。具体结构如图:
13、Chain Of Responsibility
把各个需要处理请求的对象连接成一个链或者叫管道,然后一个请求过来后,根据请求的内容,各处理对象选择对请求进行处理或者不处理。具体结构如图:
14、Command
命令对象中包含一个真正的操作对象,称之为Receiver,命令对象的执行都是通过该对象的操作行为来完成的,命令对象可以有不同的操作对象,这个是由客户系统来设置的。客户系统会创建命令对象并为它设置操作对象,同时把命令对象保存在调用者中,然后由调用者来执行命令对象,具体结构如图:
15、Interpreter
在基类定义解释操作接口,然后通过类的继承来实现不同的解释器。具体结构如图:
16、Iterator
和相应的聚合对象一一对应,由相应的聚合对象来生成,并获取相应的聚合对象,然后实现对聚合对象的遍历功能,具体结构如图:
17、Mediator
为避免对象之间过多的互相连接而产生的替代品,它的内部会引用多个对象,并把对象间的交互放在Mediator里面完成,从而对象只需要引用Mediator对象来与其他对象进行交互,具体结构如图:
18、Memento
保存对象过去的状态以便在将来可以还原,由对象创建Memento对象并保存当前状态与其中,同时将Memento对象存放在Caretaker对象里来管理。具体结构如图:
19、Observer
也叫做发布-订阅模式,发布者维护着订阅者的一个列表,当发布者发生变化时,通过调用列表中每个订阅者的接口把变化传递给订阅者,让他们发生必要的变化。具体结构如图:
20、State
客户对象维护一个State对象,State对象类型为基类,可以被任何子类替代,但都实现了相同的接口便于客户对象调用,同时State对象的操作需要客户对象提供相应的参数来实行,客户对象可以根据需求设置不同的State对象并调用该State对象的操作,这样可以减少条件判断语句,只需设置不同的State即可。具体结构如图:
21、Strategy
和State从原理上来说是一样的,一个偏向于状态,一个偏向于算法。具体结构如图:
22、Template Method
父类提供一个骨架和一些不会改变的血肉组织,剩余的根据需求在子类里实现,同时父类提供一个对外接口,把骨架、不变的血肉和变化的血肉结合在一起作为一个整体被调用。具体结构如图:
23、Visitor
一个对象有自己固定的实现方式,如果需要增加点新功能但是又不改动对象固有的结构,那么Visitor模式就可以被使用,似乎用途和Decorator没啥区别,但是结构似乎更复杂一点。客户对象提供一个接受Visitor对象的接口(所有客户对象可以继承自一个有这样一个接口的父类或者实现一个有这样一个接口的接口),并调用Visitor对象的方法来添加新功能,而Visitor对象的基类需要提供不同的接口来为不同的客户对象添加新功能。另外,可以有一个存放所有客户对象的对象,从而可以方便获取客户对象并调用Visitor方法。具体结构如图: