前言
- 收录一些自己在开发过程中比较常用的模式,整理出来以便自己梳理和复习,从而熟能生巧,举一反三。下面只列出模式的脉络大纲,取最核心的逻辑进行讲解。
- 设计模式在大多数灵活性好、可扩展性高、可移植的优秀程序中都有运用,比如界面展现层运用的MVC模式的主要关系就是由Observer(View-Model)、Composite(CompositeView嵌套视图)和Strategy(View-Controller)三个设计模式给出的。
图示符号说明
1.类图
抽象类和具体类
参与者客户类(左)和绝对客户类(右)
类关系
伪代码注解
2.对象图
一、单例(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
结构
参与者
- Singleton
定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作。
可能负责创建它自己的唯一实例。
协作
客户只能通过Singleton的Instance操作访问一个Singleton的实例。
优点
- 对唯一实例的受控访问
- 缩小名空间,避免那些存储唯一实例的全局变量污染名空间
- 允许对操作和表示的精化:Singleton类可以有子类。
- 允许可变数目的实例:这个模式使得你易于改变你的想法,并允许Singleton类有多个实例。此外,你可以用相同的方法来控制应用所使用的实例数目。
- 比类操作更灵活
(惰性初始化:它的返回值直到被第一次访问时才创建和保存)
实现
保证一个唯一的实例:常用的方法是将创建这个实例的操作隐藏在一个类操作,如通过静态成员函数作为全局访问点公布出去。
创建Singleton类的子类:指向单件实例的变量必须用子类的实例初始化,方法有三种
- 在Singleton的Instance操作中决定你要使用的是哪一个单件。
- 将Instance的实现从父类中分离出来在子类中实现。
- 使用一个静态的单件注册表,根据名字,单件实例键值对进行存储,在子类中注册供。比前面两种灵活。
二、工厂方法(Factory Method)
定义一个用于创建对象的接口,让子类决定将哪一个类实例化,Factory Method使一个类的实例化延迟到其子类。
结构
参与者
- Product
定义工厂方法所创建的对象的接口。
- ConcreteProduct
实现Product接口。
- Creator
声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象。
可以调用工厂方法以创建一个Product对象。
- ConcreteCreator
重定义工厂方法以返回一个ConcreteProduct实例。
协作
Creator依赖于它的子类来定义工厂方法,以返回一个适当的ConcreteProduct实例。
相关模式
- Abstract Factory经常用工厂方法来实现(抽象工厂中的工厂可以理解为工厂方法,相当于集合多个工厂方法);
- 工厂方法通常在Template Methods模式中被调用(一个方法可以同时是工厂方法和模板方法);
三、抽象工厂(Abstract Factory)
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
客户仅与抽象类定义的接口交互,而不使用特定的具体类的接口
结构
参与者
- AbstractFactory
声明一个创建抽象产品对象的操作接口。
- ConcreteFactory
实现创建具体产品对象的操作。
- AbstractProduct
为一类产品对象声明一个接口。
- ConcreteProduct
定义一个将被相应的具体工厂创建的产品对象。
实现AbstractProduct接口。
- Client
仅使用由AbstractFactory和AbstractProduct类声明的接口。
协作
通常在运行时刻创建一个ConcreteFactory类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象。客户应使用不同的具体工厂。
Abstract Factory将产品对象的创建延迟到它的ConcreteFactory子类。
优缺点
- 它分离了具体的类:客户通过工厂和产品的抽象接口操纵它们的实例。
- 它使得易于交换产品系列:一个具体工厂类在一个应用中仅出现一次,即在它初始化的时候,这使得改变一个应用的具体工厂变得容易。
- 它有利于产品的一致性:当一个系列的产品对象被设计在一起工作时,一个应用一次只能使用同一系列的产品对象。
- 难以支持新种类的产品:支持新产品就需要扩展工厂接口,这将涉及Abstract Factory类和所有子类的改变。
实现
- 将工作作为单件:一个应用中一般只需一个具体工厂(产品系列)
- 创建产品:使用工厂方法(Factory Method)实现。多个产品系列时可以考虑使用原型模式(Prototype),具体工厂使用产品系列中每一个产品的原型实例来初始化,且它通过复制它的原型来创建新的产品。在基于原型的方法中,使得不是每个新的产品系列都需要一个新的具体工厂类。
四、组成(Composite)
将对象组合成树形结构以表示“部分-整体”的层次关系。Composite使得用户对单个对象和组合对象的使用具有一致性。
结构
参与者
- Component
为组合中的对象声明接口。
在适当的情况下,实现所有类共有接口的缺省行为。
声明一个接口用于访问和管理Component的子组件。
(可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
- Leaf
在组合中表示叶节点对象,叶节点没有子节点。
在组合中定义图元对象的行为。
- Composite
定义有子部件的那些部件的行为。
存储子部件。
在Component接口中实现与子部件有关的操作。
- Client
通过Component接口操纵组合部件的对象。
协作
用户使用Component类接口与组合结构中的对象进行交互。如果接收者是一个叶节点,则直接处理请求。如果接收者是Composite,它通常将请求发给它的子部件,在转发请求之前或之后可能执行一些辅助操作。
实现
- 显式的父部件引用,同时父部件引用也应支持Chain of Responsibility模式。
- 共享组件:与Flyweight模式共同使用。
- 最大化Component接口:Component类尽可能涵盖Leaf和Composite类的公共操作。
- 声明管理子部件的操作:在透明性和安全性中作权衡,说明如上图。
- 在基类Component中实现一个Component列表:只有当该结构中子类的数目相对较少时,才值得使用这种方法。否则对叶节点来说会导致空间浪费。
- 子部件排序:可使用terator模式。
- 使用高速缓冲存贮改善性能:缓冲存储常用信息。
- 应该由谁删除Component:最好由Composite类负责删除其子节点。但有一种情况除外,即Leaf对象不会改变,因此可以被共享。
- 存贮组件组好用哪一种数据结构
相关模式
- 子部件-父部件连接用Chain of Responsibility模式
- Composite模式经常和Decorator模式一起使用
- Flyweight让你共享组件,但不再能引用他们的父部件
- Itertor可用来遍历Composite
- Visitor将本来应该分布在Composite和Leaf类中的操作和行为局部化
五、模板方法(Template Method)
定义一个操作中算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。(在方法体中一次性实现一个算法的不变部分,将可变的行为留给子类实现。)
结构
参与者
- AbstractClass
定义抽象的原语操作(primitive operation),具体的子类将重定义它们以实现一个算法的各步骤。
实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作。
- ConcreteClass
实现原语操作以完成算法中与特定子类相关的步骤。
协作
ConcreteClass靠AbstractClass来实现算法中不变的步骤。
效果
模板方法应该指明那些操作是钩子操作(可以被重定义)以及哪些是抽象操作(必须被重定义),子类编写者必须明确了解哪些操作是设计为有待重定义的。
相关模式
- Factory Method:在子类现象的函数中经常调用Factory Method。
- 模板方法使用继承来改变算法的一部分,Strategy使用委托来改变整个算法。
六、装饰(Decorator)
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类方式更为灵活。
结构
参与者
- Component
定义一个对象接口,可以给这些对象动态地添加职责。
- ConcreteComponent
定义一个对象,可以给这个对象添加一些职责。
- Decorator
维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。
- ConcreteDecorator
向组件添加职责。
协作
Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作。
优缺点
- 比静态继承更灵活:可以在运行时增加和删除装饰。
- 避免在层次结构高层的类有太多的特征:通过从简单的部件开始,逐步组合出复杂的功能。
- Decorator和它的Component不一样:一个被装饰的组件和这个组件是有差别的,因此不能采用对象标识去作为判断的依据(不依赖对象标识)。
- 有许多小对象
实现
- 接口的一致性:装饰对象的接口必须与它所装饰的Component的接口是一致的。
- 省略抽象的Decorator类:当仅需要添加一个职责时,可以把Decorator向Component转发请求的职责合并到唯一的ConcreteDecorator中。
- 保持Component类的简单性
- 改变对象的外壳(采用Decorator模式)与改变对象的内核(采用Strategy模式)
采用Decorator模式
采用Strategy模式
七、观察者模式(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。
结构
参与者
- Subject(目标)
目标知道它的观察者。可以有任意多个观察者观察同一个目标。
提供注册和删除观察者对象的接口。
- Observer(观察者)
为那些在目标发生改变时需获得通知的对象定义一个更新接口。
- ConcreteSubject(具体目标)
将有关状态存入各ConcreteObserver对象。
当它的状态发生改变时,向它的各个观察者发出通知。
- ConcreteObserver(具体观察者)
维护一个指向ConcreteSubject对象的引用。
存储有关状态,这些状态应与目标的状态保持一致。
实现Observer的更新接口以使自身状态与目标的状态保持一致。
协作
优缺点
- 目标和观察者间的抽象耦合;
- 支持广播通信
- 意外的更新
实现
- 创建目标到观察者之间的映射:当目标很多而观察者较少时,可以用hash表做映射关联。
- 观察多个目标:一个观察者依赖于多个目标时,目标对象可以将自己作为Update()操作的一个参数,让观察者知道应该去检查哪一个目标。多次连续操作效率低。
- 谁触发更新:一 在目标对象的SetState()(状态设定操作)中改变目标对象状态后自动调用Notify;二 让客户自己一系列操作更改完后在目标对象上调用Notify。客户可能会忘记调用,容易出错。
- 在观察者中避免对已删除目标的悬挂引用。当一个目标被删除时,让它通知它的观察者将该目标的引用复位。
- 在发出通知前确保目标的状态自身是一致的。
- 避免特定于观察者的更新协议——推/拉模型
- 显示地指定感兴趣的改变(Aspect& interest)
- 封装复杂的更新语义(更改管理器ChangeManager)
ChangeManager充当目标和观察者之间的中介者(Mediator模式),可使用Singleton模式来保证它是唯一的并且是可全局访问的。 - 结合目标类和观察者类(在不支持多重继承的语言中使用)
八、中介者(Mediator)
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。(中介者充当一个中介以使组中的对象不再相互显示引用。这些对象仅知道中介者,从而减少了相互连接的数目。)
结构
参与者
- Mediator(中介者)
中介者定义一个接口用于与各同事(Colleague)对象通信。
- ConcreteMediator(具体中介者)
具体中介者通过协调各同事对象实现协作行为。
了解并维护它的各个同事。
- Colleague class(同事类)
每一个同事类都知道它的中介者对象。
每一个同事对象在需与其他的同事通信的时候,与它的中介者通信。
协作
同事向一个中介者对象发送和接收请求。中介者在各同事间适当地转发请求以实现协作行为。
相关模式
Colleague可使用Observer模式与Mediator通信。
九、迭代器(Iterator)
提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
结构
参与者
- Iterator(迭代器)
迭代器定义访问和遍历元素的接口。
- ConcreteIterator(具体迭代器)
具体迭代器实现迭代器接口。
对该聚合遍历时跟踪当前位置。
- Aggregate(聚合)
聚合定义创建相应迭代器对象的接口。
- ConcreteAggregate(具体聚合)
具体聚合实现创建相应迭代器的接口,该操作返回ConcreteIterator的一个适当的实例。
协作
ConcreteIterator跟踪聚合中的当前对象,并能计算出待遍历的后续对象。
效果
它支持以不同的方式遍历一个聚合:复杂的聚合可以使用多种方式进行遍历。
迭代器简化了聚合的接口:有了迭代器的遍历接口,聚合本身就不再需要类似的遍历接口了。
在同一个聚合上可以同时有多个遍历
实现
谁控制该迭代:当使用该迭代器的客户来控制迭代时,该迭代器称为一个外部迭代器(推荐)。而当由迭代器控制迭代时,该迭代器称为一个内部迭代器。
外部迭代器:
var aIterator=aList.CreateIterator();
for(altertor.First();altertor.IsDone();altertor.Next()){.....}
内部迭代器:
var aTraverser=new Traverser(aList); //aTraverser内部已经包含了aIterator,在aTraverser内部对aList进行遍历
aTraverser.Traverse();
谁定义遍历算法:由迭代器负责(推荐),或由聚合本身负责,然后用迭代器(这时可称为游标)来存储当前迭代的状态,指示当前位置。
附加的迭代器操作:迭代器的最小接口由First、Next、IsDone和CurrentItem操作组成。
相关模式
- Composite:迭代器常被应用到像复合这样的递归结构上。
- FactoryMethod:多态迭代器靠Factory Method来实例化适当的迭代器子类。
- Memento:常与迭代器模式一起使用。迭代器可使用一个memento来捕获一个迭代的状态。迭代器在其内部存储memento。
十、桥接(Bridge)
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
结构
参与者
- Abstraction
定义抽象类的接口。
维护一个指向Implementor类型对象的指针。
- RefinedAbstraction
扩充由Abstraction定义的接口。
- Implementor
定义实现类的接口,该接口不一定要与Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲,Impementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作。
- ConcreteImplementor
实现Implementor接口并定义它的具体实现。
协作
Abstraction将Client的请求转发给它的Implementor对象。
优点
- 分离接口及其实现部分
- 提高可扩充性
- 实现细节对客户透明
总结
到此你已经了解开发过程中使用率高的10种设计模式。 注意,使用设计模式要考虑场景的合适性,不是一昧的套用多用就是好的,有些场景不用会更好,去繁取简。可以多参考一些开源项目上的应用。
相关资料:《设计模式》、《大话设计模式》