1、什么是设计模式
设计模式从本质上讲,是针对过去某种经验的总结。每种设计模式都是为了在特定条件下去解决某种问题。
设计模式中的每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动。它是可复用面向对象软件的基础。
设计模式解决的是系统设计问题,设计模式是“术”,设计模式背后的用意才是“道”。GoF提出了23中设计模式,是对日常用到的模式总结归纳出的,他们不是一盘散沙,是有关系的。就是对象的生命周期一步一步的将各个设计模式串联在了一起。对象的生命周期中,会一步一步的遇到总共23种设计问题,所以才会有23种设计模式。
2、设计模式原则:SOLID+CD
2.1 S - 单一职责原则
Single Responsibility Principle, SRP:一个类只能有一个让它变化的原因,即一个类只承担一个职责。
2.2 O - 开放封闭原则
Open-Close Principle, OCP:我们的设计应该对扩展开放,对修改封闭。即尽量以扩展的方式来维护系统。如果遇到需求变化,要通过添加新的类来实现,而不是修改现有的代码。
2.3 L - 里氏代换原则
Liskov Subsititution Principle, LSP:子类可以完全覆盖父类。它表示我们可以在代码中使用任意子类来替代父类并且程序不受影响,这样可以保证我们使用“继承”并没有破坏父类。
反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢(所有)动物。
2.4 I - 接口隔离原则
Interface Segregation Principle, ISP:每个接口都实现单一的功能。使用多个专门的接口,而不使用单一的总接口。添加新功能时,要增加一个新接口,而不是修改已有的接口,禁止出现“胖接口”。
2.5 D – 依赖倒置原则
Dependence Inversion Principle, DIP:这里主要是提倡“面向接口”编程,而非“面向实现”编程。具体依赖于抽象,而非抽象依赖于具体。即,要把不同子类的相同功能抽象出来,依赖与这个抽象,而不是依赖于具体的子类。
2.6 迪米特法则
又称最少知道原则(Demeter Principle)。最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
2.7 合成复用原则
Composite Reuse Principle:尽量使用合成/聚合的方式,而不是使用继承。
3、设计模式分类
根据模式的目的可分为:创建型、结构型、行为型三种。
1)创建型模式:与对象的创建有关;
2)结构型模式:处理类或对象的组合;
3)行为型模式:对类或对象怎样交互和怎样分配职责进行描述。
3.1 创建型模式(Creational Pattern)
1、 抽象工厂模式(Abstract Factory Pattern)
工厂(Factory )和产品(Product)是Abstract Factory 模式的主要参与者。该模式描述了怎样在不直接实例化类的情况下创建一系列相关的产品对象。
工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时,我们可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产,这就是抽象工厂模式的基本思想。
有时候我们希望一个工厂可以提供多个产品对象,而不是单一的产品对象,如一个电器工厂,它可以生产电视机、电冰箱、空调等多种电器,而不是只生产某一种电器。
每一个具体工厂可以生产属于一个产品族的所有产品。如果使用工厂方法模式,图4所示结构需要提供15个具体工厂,而使用抽象工厂模式只需要提供5个具体工厂,极大减少了系统中类的个数。
2、 建造者模式(Builder Pattern)
又称生成器模式。将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。为客户端返回的不是一个简单的产品,而是一个由多个部件组成的复杂产品。
它将客户端与包含多个组成部分(或部件)的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需建造者的类型即可。
建造者模式与抽象工厂模式有点相似,但是建造者模式返回一个完整的复杂产品,而抽象工厂模式返回一系列相关的产品;在抽象工厂模式中,客户端通过选择具体工厂来生成所需对象,而在建造者模式中,客户端通过指定具体建造者类型并指导Director类如何去生成对象,侧重于一步步构造一个复杂对象,然后将结果返回。如果将抽象工厂模式看成一个汽车配件生产厂,生成不同类型的汽车配件,那么建造者模式就是一个汽车组装厂,通过对配件进行组装返回一辆完整的汽车。
3、 原型模式(Prototype Pattern)
用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。通过一个原型对象克隆出多个一模一样的对象。在使用原型模式时,我们需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。
原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。
Prototype有许多和Abstract Factory和B u i l d e r一样的效果:对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。此外,这些模式使客户无需改变即可使用与特定应用相关的类。
另外一些优点:
1 ) 比其他创建型模式更为灵活,因为客户可以在运行时刻建立和删除原型。
2 ) 克隆一个原型类似于实例化一个类。可以极大的减少系统所需类的数目。
3) 改变结构以指定新对象。
4 ) 减少子类的构造。使得克隆一个原型而不是请求一个工厂方法去产生新的对象。
5) 用类动态配置应用。一些运行时刻环境允许你动态将类装载到应用中。
主要缺陷是每一个Prototype的子类都必须实现Clone操作,这可能很困难。
4、 工厂方法模式(Factory Method Pattern)
介绍定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
工厂方法模式中,一种产品(一个产品类)对应一个工厂,针对不同的产品提供不同的工厂。
主要优点:用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。 主要缺点:在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度。
5、 单例模式(Singleton Pattern)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
Singleton模式有许多优点:
1) 对唯一实例的受控访问。因为Singleton类封装它的唯一实例,所以它可以严格的控制客户怎样以及何时访问它。
2) 缩小名空间。Singleton模式是对全局变量的一种改进。它避免了那些存储唯一实例的全局变量污染名空间。
3) 允许对操作和表示的精化。Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的。你可以用你所需要的类的实例在运行时刻配置应用。
4) 允许可变数目的实例。可以用相同的方法来控制应用所使用的实例的数目。
使用Singleton模式所要考虑的实现问题:
保证一个唯一的实例。做到这一点的一个常用方法是将创建这个实例的操作隐藏在一个类操作(即一个静态成员函数或者是一个类方法)后面,由它保证只有一个实例被创建。单例模式实现!!!!!
//Singleton类定义如下:
class Singleton{
public:
//客户只能使用该成员函数访问这个单例
static Singleton* Instance();//静态方法,保证只创建一个实例
protected:
//构造函数为保护型,试图直接实例化Singleton将会编译错误
Singleton();//这就保证了仅有一个实例可以被创建
private:
//Singleton类型的指针,将用于指向Singleton的唯一的实例
static Singleton* _instance;
};
//相应的实现
Singleton* Singleton::_instance=0;//静态数据成员初始化
Singleton* Singleton::Instance(){
if(_instance==0){//只在首次创建
_instance = new Singleton;
}
return _intance;
}
实例讲解:在Windows系统中多次点击“启动任务管理器”,看能否打开多个任务管理器窗口?无论我们启动任务管理多少次,Windows系统始终只能弹出一个任务管理器窗口,也就是说在一个Windows系统中,任务管理器存在唯一性。为了确保对象的唯一性,我们可以通过单例模式来实现,这就是单例模式的动机所在。
3.2 结构型模式(Structural Pattern)
6、 适配器模式(Adapter Pattern)
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。
Adapter对Adaptee和Target进行适配,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
适配器模式的类图:
如图客户端需要调用request()方法,而适配者类Adaptee没有该方法,但是它所提供的specificRequest()方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的specificRequest()方法。
对象适配器实现如下:!!!!
#include <iostream>
using namespace std;
class Target
{
public:
Target(){}
virtual ~Target(){}
virtual void Request()
{
cout<<"Target::Request"<<endl;
}
};
class Adaptee
{
public:
void SpecificRequest()
{
cout<<"Adaptee::SpecificRequest"<<endl;
}
};
class Adapter : public Target
{
public:
Adapter() : m_Adaptee(new Adaptee) {}
~Adapter()
{
if (m_Adaptee != NULL)
{
delete m_Adaptee;
m_Adaptee = NULL;
}
}
void Request()
{
m_Adaptee->SpecificRequest();
}
private:
Adaptee *m_Adaptee;
};
int main()
{
Target *targetObj = new Adapter();//基类指针指向子类对象
targetObj->Request();调用适配后的Request()方法
delete targetObj;
targetObj = NULL;
return 0;
}
实例讲解:
我的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?答案是引入一个电源适配器(AC Adapter),俗称充电器,有了它,生活用电和笔记本电脑即可兼容。
7、 桥接模式(Bridge Pattern)
可以将抽象部分与它的实现部分分离,使它们都可以独立地变化。
Bridge模式的目的就是允许分离的类层次一起工作,即使它们是独立演化的。
如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使两者可以独立扩展,让系统更加符合“单一职责原则”。
桥接模式用一种巧妙的方式处理多重继承存在的问题,用抽象关联取代了传统的多重继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同时有效控制了系统中类的个数。
使用桥接模式实例:两个维度(毛笔大小和毛笔颜色)分开。针对两个不同的维度提取抽象类和实现类接口,并建立一个抽象关联关系。
上图中,如果需要增加一种新型号的毛笔,只需扩展左侧的“抽象部分”,增加一个新的扩充抽象类;如果需要增加一种新的颜色,只需扩展右侧的“实现部分”,增加一个新的具体实现类。
8、 组合模式(Composite Pattern)
组合模式描述了面向对象递归组合的本质。
将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。!!!
9、 装饰模式(Decorator Pattern)
动态地给一个对象添加一些额外的职责。就扩展功能而言,它比生成子类方式更为灵活。
装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为。对已有对象(新房)的功能进行扩展(装修),以获得更加符合用户需求的对象,使得对象具有更加强大的功能。
10、 外观模式(Facade Pattern)
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
在软件开发中,有时一个客户类需要和多个业务类交互,特别需要一个类似服务员一样的角色,由它来负责和多个业务类进行交互,而客户类只需与该类交互。外观模式通过引入一个新的外观类(Facade)来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。降低了系统的耦合度。!!!!
11、 享元模式(Flyweight Pattern)
运用共享技术有效地支持大量细粒度的对象。
将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。
避免系统中出现大量相同或相似的对象,同时又不影响客户端程序通过面向对象的方式对这些对象进行操作。享元模式正为解决这一类问题而诞生。享元模式通过共享技术实现相同或相似对象的重用,在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象。
12、 代理模式(Proxy Pattern)
介绍为其他对象提供一个代理以控制对这个对象的访问。
当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。
3.3 行为型模式(Behavioral Pattern)
13、 责任链模式(Chain of Responsibility Pattern)
为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
14、 命令模式(Command Pattern)
该模式描述了怎样封装请求,也描述了一致性的发送请求的接口,允许你配置客户端以处理不同请求。对请求排队或记录请求日志,以及支持可取消的操作。
15、 解释器模式(Interpreter Pattern)
给定一个语言,定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
16、 迭代器模式(Iterator Pattern)
支持访问和遍历对象结构。不仅可用于组合结构也可用于集合。介绍提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
17、 中介者模式(Mediator Pattern)
介绍用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
18、 备忘录模式(Memento Pattern)
介绍在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
19、 观察者模式(Observer Pattern)
介绍定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
20、 状态模式(State Pattern)
介绍允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
21、 策略模式(Strategy Pattern)
在对象中封装算法是策略模式的目的。允许不同的格式化算法。
介绍定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。
模式的主要参与者是Strategy对象(这些对象中封装了不同的算法)和它们的操作环境。Strategy模式应用的关键点在于为Strategy和它的环境设计足够通用的接口,以支持一系列的算法。你不必为了支持一个新的算法而改变Strategy或它的环境。
22、 模板方法模式(Template Method Pattern)
介绍定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
主要解决在软件构建过程中,对于某一项任务,常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因而无法和任务的整体结构同时实现。
23、 访问者模式(Visitor Pattern)
介绍表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
它允许对图元结构所作分析的数目不受限制地增加而不必改变图元类本身。
访问者类的另一个优点是它不局限使用于像图元结构这样的组合者,也适用于其他任何对象结构。包括集合、列表,甚至无环有向图。再者,访问者所能访问的类之间无需通过一个公共父类关联起来。也就是说,访问者能跨越类层次结构。
参考:
《设计模式》