点此下载《完整的设计模式学习笔记PDF版本》
《设计模式》学习笔记
Blog: http://blog.csdn.net/hannosogno
E-mail:chuanqi.tan(at)gmail.com
所有的模式都有详细的C++实现代码,完整的代码及工程见Blog
转载及引用请注明
By Hannosogno @ BIT 2010
引言
l 设计模式可以想象为是面向对象软件的设计经验。可以说设计模式就是解决某个特定的面向对象软件问题的特定方法。
l 23种设计模式的组织编目
设计模式在粒度和抽象层次上各不相同。由于存在众多的设计模式,我们希望用一种方式将它们组织起来。这一节将对设计模式进行分类以便于我们对各种相关的模式进行引用。分类有助于更快地学习目录中的模式,且对发现的模式也有指导作用,如下表所示。
|
目的 |
|||
创建型 |
结构型 |
行为型 |
||
范围 |
类 |
Factory Method |
Adapter(类) |
Interpreter |
对象 |
Abstract Factory |
Adapter(对象) |
Chain of Responsibility |
我们根据两条准则对模式进行分类。
§ 第一是目的准则,即模式是用来完成什么工作的。模式依据其目的可分为创建型(Creational)、结构型(Structural)、行为型(Behavioral)三种。创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象怎样交互和怎样分配职责进行描述。
§ 第二是范围准则,指定模式主要是用于类还是用于对象。类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来。对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性。从某种意义上来说,几乎所有模式都使用继承机制,所以“类模式”只指那些集中于处理类间关系的模式,而大部分模式都属于对象模式的范畴。
l 创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一个对象中。
结构型类模式使用继承机制来组合类,而结构型对象模式则描述了对象的组装方式。
行为型类模式使用继承描述算法和控制流,而行为型对象模式则描述一组对象怎样协作完成单个对象所无法完成的任务。
还有其他组织模式的方式。有些模式经常会被绑定在一起使用。
有些模式是可替代的,有些模式尽管使用意图不同,但产生的设计结果是很相似的。
l 客户请求是使对象执行操作的唯一方法,操作又是对象改变内部数据的唯一方法。由于这些限制,对象内部状态是被封装的,它不能被直接访问,它的表示对于外部是不可见的。
l 针对接口编程,而不是针对实现编程。
l 优先使用对象组合,而不是类继承。
l 因为继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装性”。子类中的实现与它的父类有如此紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。所以要优先使用对象组合,而不是类继承。
一个可用的解决方法就是只继承抽象类,因为抽象类通常提供较少的实现。这个方案在Effective C++中也被反复的提及。
l 委托是一种组合方法,它使组合具有与继承相同的复用能力,在委托方式下,有两个对象参与处理一个请求,接受请求的对象将操作委托给它的代理者。
委托是对象组合的特例,它告诉你对象组合作为一个代码复用机制可以替代继承。(委托就是普通的组合嘛,没看到有什么特殊的啊!只是将请求转发而已,但是这种思想可以替代继承了。)
l 现在,可复用面向对象软件系统现在一般划分为三大类:应用程序、工具箱和框架(Framework),我们平时开发的具体软件都是应用程序;Java的API属于工具箱;而框架是构成一类特定软件可复用设计的一组相互协作的类。EJB(EnterpriseJavaBeans)是Java应用于企业计算的框架.
l 除非是为了学习目的,否则只有当一个模式提供的灵活性是真正需要的时候,才有必要使用。
23种基本设计模式的简单解释:
1. AbstractFactory:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
2. Adapter:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
3. Bridge:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
4. Builder:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
5. Chainof Responsibility:为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
6. Command:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
7. Composite:将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。
8. Decorator:动态地给一个对象添加一些额外的职责。就扩展功能而言, 它比生成子类方式更为灵活。
9. Facade:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
10. FactoryMethod:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。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. TemplateMethod:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
23. Visitor:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
创建型模式
创建型模式抽象了实例化过程。在这些模式中有两个不断出现的主旋律:第一,它们都将关于该系统使用哪些具体的类的信息封装起来。第二,它们隐藏了这些类的实例是如何被创建和放在一起的。整个系统关于这些对象所知道的是由抽象类所定义的接口。
创建型模式的总结
Factory Method使一个设计可以定制且只略微有一点复杂。其它的设计模式需要新的类,而Factory Method只需要一个新的操作。人们通常将Factory Method作为一种标准的创建对象的方法。(这也是NVI原则的标准体现)
使用Abstract Factory、Prototype或Builder的设计甚至比使用Factory Method的那些设计更要灵活,但它们也更加复杂。
通常,设计以使用Factory Method开始,并且当设计者发现需要更大的灵活性时,设计便会向其它创建型模式演化。
各个模式的小结:
- 抽象工厂:生成一系列的产品时使用,对每个系列都有一个具体工厂来负责创建这个系列的产品
- Builder:一步一步的生成复杂的对象产品
- 工厂方法:最基本的创建对象的虚方法
- Prototype:也是生成一系列的产品,而且具有更大的灵活性,更少的类的数目。代价就是每个产品必须实现自我复制
- Singleton:保证一个类只有一个实例,应该使用标准的静态方法来实现,标准的实现方法完美无暇
Singleton(单件)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用性:
- 当类只能有一个实例而且客户可以从一个从所周知的访问点访问它时
- 当这个唯一的实例应该是通过子类化可扩展的(protected修饰构造函数),并且客户应该无需更改代码就能使用一个扩展的实例时。
效果:
- Singleton模式是对全局变量的一种改进,它避免了那些存储唯一实例的全局变量污染命名空间。
- Singleton可以有子类,而且有这个扩展类的实例来配置一个应用是很容易的
- 使用Instance()函数来lazy初始化Singleton避免了C++中编译单元之间静态对象的依赖问题,这是一种非常好非常常用的手段,用静态对象代替全局变量时都应该使用这种方法
Singleton
#include <iostream>
using namespace std;
//标准写法实现的单件模式
class Singleton
{
protected:
//一般将构造方法实现为受保护的,如果不需要被扩展,也可以实现为私有的
Singleton()
{
cout << "A old style singleton constructor"<< endl;
}
private:
//一个私有的静态成员
staticSingleton * _instance;
public:
//公有的Singleton方法,返回该类的唯一实例
staticSingleton * Instance()
{
if(NULL == _instance)
{
//惰性(lazy)初始化,只有使用时才初始化
_instance = new Singleton();
}
return_instance;
}
voidDisplay()
{
cout << "Old singleton display" << endl;
}
};
Singleton *Singleton::_instance = NULL; //初始化为空指针
//EffectiveC++上介绍的新的Singleton写法
class NewSingleton
{
private:
NewSingleton()
{
cout << "New style singleton constructor"<< endl;
}
public:
/*
新的写法省略了检查NULL==_instance的过程,理论效率更好
而且减少了实现单件的代码量
真是太棒了!
*/
staticNewSingleton& Instance()
{
staticNewSingleton _instance;
return_instance;
}
voidDisplay()
{
cout << "New singleton display" << endl;
}
};
void main()
{
Singleton::Instance()->Display();
Singleton::Instance()->Display();
Singleton::Instance()->Display();
Singleton::Instance()->Display();
Singleton::Instance()->Display();
Singleton::Instance()->Display();
NewSingleton::Instance().Display();
NewSingleton::Instance().Display();
NewSingleton::Instance().Display();
NewSingleton::Instance().Display();
NewSingleton::Instance().Display();
NewSingleton::Instance().Display();
}
事实上,还可以更灵活一点,将Instance()方法实现如下:
//公有的Singleton方法,返回该类的唯一实例
static Singleton* Instance()
{
if(NULL == _instance)
{ //惰性(lazy)初始化,只有使用时才初始化
string singleton_set =get_set_from_file("singleton");
if(singleton_set == "a")
{
_instance = new ConcreteSingleton1();
}
elseif (singleton_set == "b")
{
_instance = new ConcreteSingleton2();
}
else
{
_instance = new DefaultSingleton();
}
}
return_instance;
}
不过一般的,基本的实现就非常好了。
Prototype(原型)
用原型实例指定创建对象的种类,并且通过
Copy
这些原型创建新的对象。
在C++这种静态语言中,类不是对象,并且运行时刻只能得到很少或者得不到任何类型信息,所以Prototype特别有用。而在那些支持反射特性的语言,如C# java中,Prototype就显得不那么重要了。
适用性:
- 当一个系统应该独立于它的产品创建、构成、表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时,建立相应数目的原型并Clone它们可能比每次用合适的状态手工实例化该类更方便一些。
优点:
- 允许在运行时动态加载或动态组合出新类,它比其它创建模式更灵活,因为客户可以在运行时刻建立和删除原型。
- 这种设计使得用户无需编程即可定义新“类”
- 能够大大的减少子类的构造,仅需要定义它们的特点类即可
缺点:
- 主要缺陷是每一个Prototype的子类都必须实例Clone操作,这可能很困难。例如,当所考虑的类已经存在时就难以新增Clone操作。当内部包括一些不支持拷贝或有循环引用的对象时,实现克隆可能也会很困难。
转载一个很好的比喻:
举一个例子来解释这个模式的作用,假设有一家店铺是配钥匙的,他对外提供配制钥匙的服务(提供Clone接口函数),你需要配什么钥匙它不知道只是提供这种服务,具体需要配什么钥匙只有到了真正看到钥匙的原型才能配好.也就是说,需要一个提供这个服务的对象,同时还需要一个原型(Prototype),不然不知道该配什么样的钥匙.
Prototype和Abstract Factory模式在某种方面是相互竞争的,它是它们也是可以一起使用。Abstract Factory可以存储一个被克隆的原型的集合,并且返回产品对象。
感觉上,prototype模式与abstract factory模式都是用来产生一系列的产品,由于Prototype模式可以进行随意组合,极大的减少了类的数目,代价就是每个产品必须能够自我复制。
Prototype
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
using namespace std;
//原型的虚基类
class Prototype
{
public:
Prototype(conststring &state)
: _state(state)
{}
string State()
{
return_state;
}
virtual~Prototype(){}
//一般来说,这个都为纯虚函数,很难提供一个好的默认实现
virtualPrototype* clone() = 0;
protected:
string _state;
};
//实际派生类1
class ConcretePrototype1 : publicPrototype
{
public:
ConcretePrototype1(const string &state)
: Prototype(state)
{}
virtual~ConcretePrototype1(){}
virtualPrototype* clone()
{
returnnew ConcretePrototype1(_state);
}
};
//实际派生类2
class ConcretePrototype2 : publicPrototype
{
public:
ConcretePrototype2(const string &state)
: Prototype(state)
{}
virtual~ConcretePrototype2(){}
virtualPrototype* clone()
{
returnnew ConcretePrototype2(_state);
}
private:
string _state;
};
int _tmain(int argc,_TCHAR* argv[])
{
Prototype *p1 = new ConcretePrototype1("Prototypestate 1");
Prototype *p2 = new ConcretePrototype1("Prototypestate 2");
cout << p1->State() <<endl;
cout << p2->State() <<endl;
Prototype *p3 = p1->clone();
Prototype *p4 = p2->clone();
cout << p3->State() <<endl;
cout << p4->State() <<endl;
return0;
}
Factory Method(工厂方法)
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
可以这么理解:一个工厂方法就是一个真正负责创建对象的“虚”方法。这个UML中的operation(),它看起来像是在创建对象,实际上它在调用factoryMethod()来实际创建对象,由于factoryMethod是虚方法,所以可以通过子类重载ConcreteCreator来重写父类的虚方法。(甚至Creator类的factoryMethod是一个纯虚方法)。
operator()是public的,属于接口的一部分。而factoryMethod()可以是protected或者private的,这也满足NVI(None Virtual Interface非虚接口)的要求。
工厂方法用来生产单个产品,即使这样实现,所生产的产品也必须满足product接口。如果需要生产一系列不同的产品,应该使用抽象工厂模式。
根据情况,工厂方法可以可以有一个缺省的实现,也可以没有一个缺省的实现。
C++中的工厂方法都是虚函数并且常常是纯虚函数。而且一定要注意不能在Creator的构造函数中调用工厂方法,因为在构造函数中多态性是不可用的。
C++中可以使用模板来避免创建子类。
Abstract Factory模式经常是用工厂方法来实现(因为Abstract Factory可以视为一系列工厂方法的集合)。工厂方法通常在TemplateMethods中被调用。Prototypes不需要创建Creator的子类。但是,它们通常要求一个针对Product类的Initialize操作。Creator使用Initialize来初始化对象,而Factory Method不需要这样的操作。
FactoryMethod
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
using namespace std;
class AbstractProduct
{
public:
virtual~AbstractProduct(){}
virtualvoid product_active() = 0;
};
class DefaultProduct : publicAbstractProduct
{
public:
virtualvoid product_active()
{
cout << "DefaultProduct actived!" << endl;
}
};
class ConcreteProduct : publicAbstractProduct
{
public:
virtualvoid product_active()
{
cout << "ConcreteProduct actived!" <<endl;
}
};
class Creator
{
public:
Creator()
{}
//这个方法看起来在创建对象,实际上它是调用虚方法来创建对象
voidGenerateProduct()
{
_product = create_product();
}
AbstractProduct * GetProduct()
{
return_product;
}
protected:
//FactoryMethod
//带缺省实现,如果不需要缺省实现,此方法可以定义为纯虚函数
virtualAbstractProduct * create_product()
{
returnnew DefaultProduct();
}
private:
AbstractProduct *_product;
};
class ConcreteCreator : publicCreator
{
public:
ConcreteCreator(){}
protected:
//有一个string product_type可以实现子类中仅仅重写感兴趣的产品
virtualAbstractProduct * create_product()
{
returnnew ConcreteProduct();
//不感兴趣的产品交给缺省实现来完成
//returnCreator::create_product();
}
};
int _tmain(int argc,_TCHAR* argv[])
{
Creator *c = newConcreteCreator();
c->GenerateProduct();
c->GetProduct()->product_active();
return0;
}
Abstract Factory(抽象工厂)
抽象工厂提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。
在以下情况下可以使用Abstract Factory模式
- 一个系统要独立于它的产品的创建、组合和表示时
- 一个系统要由多个产品系列中的一个来配置时
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时
- 当你要提供一个产品类库,而只想显示它们的接口而不是实现时
效果与实现:
- 有几种可变的方案,就有几个具体工厂来产生这些产品
- 它分离了具体的类,把所有的创建产品的任务交给一个工厂来完成。抽象类只是一个接口
- 易于交换产品系列:通过使用不同的具体工厂就可以方便的交换整个产品系列
- 它有利于产品的一致性:当一个系列中的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这点很重要。而Abstract Factory可以完美的实现这一点
- 一个具体的工厂通常是一个Singleton
- 最通常的办法就是为每一个产品定义一个工厂方法(Factory Method)。
- 由于具体工厂通常要重新实现所有的产品生产方法,即使它们的差别很小。这样就可以通过与Prototype模式结合来减少工作量
- 一个常见的技巧就是使得AbstractFactory类不是抽象类,但是它的每一个产品创建方法都是虚方法。这样使得从AbstractFacotry派生出一个具体工厂来变得非常容易,只需要重写必须改变的方法即可,而不必重写所有的创建产品的方法。
- Abstract Factory类通常用工厂方法(Factory Method)实现,但它们也可以用Prototype实现
自我总结:
- 抽象工厂模式一直强制的是创建一系列的产品。
- Abstract Factory的一个常见应用是用于创建不同视感风格的界面
AbstractFactory
#include "stdafx.h"
#include <string>
#include <iostream>
using namespace std;
class ProductA
{
public:
ProductA(conststring &sign)
: _sign(sign)
{}
string Sign()
{
return_sign;
}
private:
string _sign;
};
class ProductB
{
public:
ProductB(conststring &sign)
: _sign(sign)
{}
string Sign()
{
return_sign;
}
private:
string _sign;
};
class AbstractFactory
{
public:
virtual~AbstractFactory(){}
virtualProductA * GetProductA() = 0;
virtualProductB * GetProductB() = 0;
};
class ConcreteFactory1 : publicAbstractFactory
{
public:
virtualProductA * GetProductA()
{
returnnew ProductA("ProductAmade by ConcreteFactory1");
}
virtualProductB * GetProductB()
{
returnnew ProductB("ProductBmade by ConcreteFactory1");
}
};
class ConcreteFactory2 : publicAbstractFactory
{
public:
virtualProductA * GetProductA()
{
returnnew ProductA("ProductAmade by ConcreteFactory2");
}
virtualProductB * GetProductB()
{
returnnew ProductB("ProductBmade by ConcreteFactory2");
}
};
class Client
{
public:
Client(AbstractFactory *factory)
{
_pA =factory->GetProductA();
_pB =factory->GetProductB();
}
ProductA * GetProductA()
{
return_pA;
}
ProductB * GetProductB()
{
return_pB;
}
private:
ProductA * _pA;
ProductB * _pB;
};
int _tmain(int argc,_TCHAR* argv[])
{
AbstractFactory *f1 = new ConcreteFactory1();
AbstractFactory *f2 = new ConcreteFactory2();
Client c1(f1);
cout <<c1.GetProductA()->Sign() << endl;
cout <<c1.GetProductB()->Sign() << endl;
Client c2(f2);
cout <<c2.GetProductA()->Sign() << endl;
cout <<c2.GetProductB()->Sign() << endl;
deletef1;
deletef2;
return0;
}
Builder(生成器)
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
以下情况适用Builder模式:
- 当创建复杂算法应该独立于该对象的组成部分以及它们的装配方式时
- 当构造过程必须允许被构造的对象有不同的表示时
各个部件之间的协作流程:
Builder模式与一下子就生成产品的创建型模式不同,它是在导向者的控制下一步一步构造产品的。仅当该产品完成时导向者才从生成器中取回它。
编译子系统中的Parser类就是一个Director,它以一个ProgramNodeBuilder对象作为构造的参数。每当Parser对象识别出一个语法结构时,它就通知它的ProgramNodeBuilder对象。
Abstract Factory与Builder相似,因为它们都可以用来创建复杂对象。主要的区别是Builder模式着重于一步步构造一个复杂对象。而Abstract Factory着重于多个系列的产品对象(简单的或复杂的)。Builder在最后一步返回产品,而对于Abstract Factory来说,产品是主即返回的。
生成器模式强调的是一步一步的构建对象!
Builder
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
using namespace std;
class Builder
{
public:
// 方法不为纯虚函数
// 使得子类可以只重写它们感兴趣的实现
// 当然也可以使用通常的纯虚函数实现方法
virtualvoid BuilderPart(string aPart){}
virtualstring GetResult(){ return NULL; }
};
class SimpleBuilder : publicBuilder
{
public:
virtualvoid BuilderPart(string aPart)
{
_result << aPart<< endl;
}
virtualstring GetResult()
{
return_result.str();
}
private:
stringstream _result;
};
class Director
{
public:
Director(Builder *builder)
{
_builder = builder;
vector<string> _parts;
_parts.push_back("Part A");
_parts.push_back("Part B");
_parts.push_back("Part C");
for(vector<string>::iterator iter = _parts.begin(); iter != _parts.end();++iter)
{//一步一步的构建好对象
_builder->BuilderPart(*iter);
}
}
string GetResult()
{
return_builder->GetResult();
}
private:
Builder *_builder;
string _result;
};
int _tmain(int argc,_TCHAR* argv[])
{
Builder *b = newSimpleBuilder();
Director d(b);
cout << d.GetResult() <<endl;
return0;
}
结构型模式
结构型模式涉及到如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。
结构型模式的讨论
结构型模式具有很大的相似性,都是通过对象的继承、组合来实现的。
模式之间最重要的是它们之间的差异,理解它们的目的以及使用场合。
Adapter与Bridge:
- Adapter模式主要是为了解决两个已有接口之间的不匹配;Bridge则对抽象接口与它的实现部分进行分离并桥接
- Adapter为了使两个独立的接口能相互协调工作;Bridge则是为了给用户提供一个稳定的接口(类库二进制上的兼容性)
- Adapter在类已经设计好之后实施;Bridge模式在设计类之前实施
Facade与Adapter区别在于:Facade定义了一个新的接口,而Adapter则复用了原有的接口。适配器使两个已有的接口协同工作,而不是定义一个新的接口。
Composite与Decorator:
- Decorator旨在使你能够不需要生成子类即可以给对象添加职责;Composite则旨在构造类,使多个相关的对象能够以统一的方式进行处理,而多重对象可以被当作一个对象来处理。
- Composite与Decorator通常协同使用:这时系统中将会有一个抽象类,它有一些Composite子类和Decorator子类,还有一些实现系统的基本构建模块。此时,composites和decorator将拥有共同的接口。从Decorator角度来看,Composite是一个ConcreteComponent。而从Composite模式的角度来看,Decorator则是一个Leaf。