文章目录
一、预热
什么是设计模式:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。
设计模式目标:可复用
思维模式比代码技巧更重要,设计原则比模式更重要!
二、设计原则
- 依赖倒置原则(DIP)
稳定应该依赖于稳定,不能有对变化的依赖。对变化应该隔离开。 - 开放封闭原则(OCP)
对扩展开放,对更改封闭。类模块应该是可扩展的,但是不可修改。 - 单一职责原则(SRP)
一个类应该仅有一个引起它变化的原因,变化的方向隐含着类的责任。 - 里氏代换原则 Liskov(LSP)
子类必须能替换它们的基类(IS-A),继承表达类型抽象。 - 接口隔离原则(ISP)
接口应该小而完备。 - 优先使用对象组合,而不是类继承
继承在某种程度上破坏了封装性,子类父类耦合度高。但也不是说不能用继承,某些情况下可能继承更适合。 - 封装变化点
将稳定和变化的部分隔离开,出现变化时,稳定部分不需要改动。 - 针对接口编程,而不是针对实现编程
不将变量类型声明为某个特定的具体类,而是声明为某个接口。
客户程序无需知道对象的具体类型,只需要知道对象所具有的接口。
减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。
三、GOF-23模式分类、重构
3.1 模式分类
- 从目的来看:
- 创建型( Creational)模式: 将对象的部分创建工作延迟到类或者其他对象,从而应对需求变化为对象创建时具体类型现引来的冲击。
包括:单例模式,简单工厂模式,抽象工厂模式,工厂方法模式,原型模式,建造者模式 - 结构型( Structural)模式: 通过类继承或者对象组合获得更活的结构,从而应对需求变化为对象的结构带来的冲击。
包括:适配器模式,桥接模式,组合模式,装饰模式,外观模式,享元模式,代理模式 - 行为型( Behavioral)模式: 通过类继承或者对象组合来划类与对象间的职责,从而应对需求变化为多个交互的对象带的冲击。
包括:职责链模式,命令模式,解释器模式,迭代器模式,中介者模式,备忘录模式,观察者模式,状态模式,策略模式,模板方法模式,访问者模式
- 从范围来看:
- 类模式处理类与子类的静态关系。
- 对象模式处理对象间的动态关系。
3.2 重构关键技法
- 静态 —> 动态
- 早绑定 —> 晚绑定
- 继承 —> 组合
- 编译时依赖 —> 运行时依赖
- 紧耦合 —> 松耦合
四、TemplateMethod Mode 模板方法模式
定义一个操作中的算法的骨架 (稳定),而将一些步骤延迟(晚绑定)到子类中。 Template ethod使得子类可以不改变(复用)一个算法的结构即可重定义(override 重写)该算法的某些特定步骤。
模板方法实现了一个算法的框架,有些步骤可能是固定不变的,不需要暴露给用户,而有些步骤是允许用户重写的,这些步骤会被封装成一个或多个方法,以虚函数的形式提供给用户,允许用户自己实现。
在下面的代码中,抽象基类的TemplateMethod方法中可能还有其它的步骤,不止这两个。用户只需要根据实际情况重写这些接口即可,不用关心整体的工作流程。(稳定的框架为非虚方法,可变化的步骤为虚方法)
#include <iostream>
using namespace::std;
class AbstractClass
{
public:
void TemplateMethod() {
CustomOperation1();
CustomOperation2();
}
protected:
//抽象基类定义的默认Operation
virtual void CustomOperation1() {
cout << "Default Operation1." << endl;
}
virtual void CustomOperation2() {
cout << "Default Operation2." << endl;
}
};
class ConcreteClassA : public AbstractClass
{
public:
//具体类重写的Operation
virtual void CustomOperation1() {
cout << "ConcreteA Operation1." << endl;
}
virtual void CustomOperation2() {
cout << "ConcreteA Operation2." << endl;
}
};
class ConcreteClassB : public AbstractClass
{
public:
//具体类重写的Operation
virtual void CustomOperation1() {
cout << "ConcreteB Operation1." << endl;
}
virtual void CustomOperation2() {
cout << "ConcreteB Operation2." << endl;
}
};
int main(void)
{
AbstractClass * pAbstract = new AbstractClass();
pAbstract->TemplateMethod();
AbstractClass * pConcreteA = new ConcreteClassA();
pConcreteA->TemplateMethod();
AbstractClass * pConcreteB = new ConcreteClassB();
pConcreteB->TemplateMethod();
return 0;
}
五、Strategy Mode 策略模式
开放封闭原则
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。该模式使得算法可独立于使用它的客户而变化。
策略模式其实是对算法的封装,当处理一个问题可能有多种算法(方法)时就可以使用该模式。Strategy类就是对该算法的封装,具体类则是对该算法的不同实现。
当程序中出现if…else…或者switch…case…,而且很有可能会增加分支时,一般就要策略模式了。
#include <iostream>
#include <string>
using namespace std;
class Strategy
{
public:
virtual ~Strategy() { }
virtual void AlgorithmInterface() = 0;
};
//ConcreteStrategyA、ConcreteStrategyB对同一个算法的接口做了不同的实现
class ConcreteStrategyA : public Strategy
{
public:
virtual void AlgorithmInterface()
{
cout << "AlgorithmInterface Implemented by ConcreteStrategyA." << endl;
}
};
class ConcreteStrategyB : public Strategy
{
public:
virtual void AlgorithmInterface()
{
cout << "AlgorithmInterface Implemented by ConcreteStrategyB." << endl;
}
};
//Context代表上下文
class Context
{
public:
~Context()
{
delete m_Strategy;
}
Context(Strategy * pStrategy) : m_Strategy(pStrategy) { }
void ContextInterface()
{
if (NULL != m_Strategy)
{
//上下文根据需要调用算法的某个实现
m_Strategy->AlgorithmInterface();
}
}
private:
Strategy * m_Strategy;
};
int main(void)
{
//情况1: 调用该算法时,我需要使用ConcreteStrategyA实现的方法
Strategy * pConcreteStrategyA = new ConcreteStrategyA();
Context * pContext = new Context(pConcreteStrategyA);
//...
pContext->ContextInterface();
//...
//情况2: 调用该算法时,使用ConcreteStrategyB实现的方法更为合适
Strategy * pConcreteStrategyB = new ConcreteStrategyB();
Context * pContext2 = new Context(pConcreteStrategyB);
//...
pContext2->ContextInterface();
//...
return 0;
}
模板方法和策略模式对比:两者都是通过继承基类对接口进行具体的实现。不同之处在于前者是对某个算法流程中的多个步骤,后者是对某一个算法在不同情况下的不同实现。同时策略模式的上下文还需要维护一个算法基类的指针。
六、Observer Mode 观察者模式
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。当一个对象发生了变化,关注它的对象就会得到通知;这种交互也称为发布-订阅(publish-subscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者。
目标不需要知道具体的观察者有什么接口以及有多少个观察者,观察者只需要实现一个update接口供目标通知时使用即可。如以下示例代码所示,当目标状态发生变化时,目标调用Notify接口自动通知所有的观察者(其实是目标调用了观察者的update接口,主动更新了观察者的状态,这样观察者就不用做任何事情就可以得到更新)
#include <iostream>
#include <string>
#include <list>
using namespace std;
class Subject;
class Observer
{
public:
~Observer() {}
virtual void update(Subject *pSubject) = 0;
};
class Subject
{
public:
~Subject()
{
m_observer_list.clear();
}
void Attach(Observer * pObserver)
{
cout << "Attach an Observer: " << hex << pObserver << endl;
m_observer_list.push_back(pObserver);
}
void Detach(Observer * pObserver)
{
cout << "Detach an Observer: " << hex << pObserver << endl;
m_observer_list.remove(pObserver);
}
virtual void Notify()
{
list<Observer *>::iterator it;
for (it = m_observer_list.begin(); it != m_observer_list.end(); ++it)
{
cout << "Notify the Observers: " << hex << *it << endl;
(*it)->update(this);
}
}
virtual void SetStatus(int iStatus) = 0;
virtual int GetStatus() = 0;
private:
list<Observer *> m_observer_list;
};
class ConcreteSubject : public Subject
{
public:
virtual void SetStatus(int iStatus)
{
m_status = iStatus;
}
virtual int GetStatus()
{
return m_status;
}
private:
int m_status;
};
class ConcreteObserver : public Observer
{
public:
virtual void update(Subject *pSubject)
{
m_status = pSubject->GetStatus();
cout << "ConcreteObserver "<< hex << this << " get notify, status = " << m_status << endl;
}
private:
int m_status;
};
int main(void)
{
Subject * pConcreteSubject = new ConcreteSubject();
Observer * ConcreteObserver1 = new ConcreteObserver();
Observer * ConcreteObserver2 = new ConcreteObserver();
pConcreteSubject->Attach(ConcreteObserver1);
pConcreteSubject->Attach(ConcreteObserver2);
pConcreteSubject->SetStatus(2);
pConcreteSubject->Notify();
return 0;
}
运行结果:
七、Decorator Mode 装饰模式
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
装饰模式能动态地为某个类的对象添加职责,这种添加是通过外部(装饰类)、采用对象组合的方式实现的,原有的类无需改动,且对使用者来说接口是一致的。
在示例代码中,ConcreteComponent是原本存在的类,现在需要对这个类的对象添加职责,于是定义一个Decorator基类和ConcreteDecorator子类。Decorator基类中需要维护一个Component指针,以及一个和ConcreteComponent类一致的接口(Operation);ConcreteDecorator子类也有一个和ConcreteComponent类一致的接口(Operation),以及添加职责的方法(AddOperation)。当调用具体修饰类的对象的Operation接口时,一方面会执行原有的职责(即ConcreteComponent类对象的Operation),另一方面也会执行新添加的职责,这样就为类对象添加了一个新的职责。
#include <iostream>
using namespace std;
class Component
{
public:
virtual void Operation() = 0;
};
//ConcreteComponent是需要添加职责的对象所属的类
class ConcreteComponent : public Component
{
public:
void Operation()
{
cout << "This is original operation." << endl;
}
};
class Decorator : public Component
{
public:
Decorator(Component *pComponent) : m_pComponent(pComponent) {}
void Operation() //提供一个和Component一致的接口
{
m_pComponent->Operation(); //执行原有职责
}
private:
Component *m_pComponent; //维护一个Component指针,用来执行原有职责
};
//ConcreteDecorator:为ConcreteComponent类的对象添加职责(ConcreteDecorator装饰ConcreteComponent)
class ConcreteDecorator : public Decorator
{
public:
ConcreteDecorator(Component *pComponent) : Decorator(pComponent) {}
void Operation()
{
Decorator::Operation(); //执行原有职责
AddOperation(); //添加职责
}
void AddOperation()
{
cout << "This is Added Operation." << endl;
}
};
int main(void)
{
Component *pConcreteComponent = new ConcreteComponent();
Decorator *pConcreteDecorator = new ConcreteDecorator(pConcreteComponent);
pConcreteDecorator->Operation();
return 0;
}
执行结果:
扩展:
装饰模式是为类的对象添加职责,而不是对类。使用装饰模式能灵活地组合所需要的功能,例如可以在示例代码的基础上再定义一个ConcreteDecorator类的对象,这样就新增了两个功能:
#include <iostream>
using namespace std;
class Component
{
public:
virtual void Operation() = 0;
};
//ConcreteComponent是需要添加职责的对象所属的类
class ConcreteComponent : public Component
{
public:
void Operation()
{
cout << "This is original operation." << endl;
}
};
class Decorator : public Component
{
public:
Decorator(Component *pComponent) : m_pComponent(pComponent) {}
void Operation() //提供一个和Component一致的接口
{
m_pComponent->Operation(); //执行原有职责
}
private:
Component *m_pComponent; //维护一个Component指针,用来执行原有职责
};
//ConcreteDecorator:为ConcreteComponent类的对象添加职责(ConcreteDecorator装饰ConcreteComponent)
class ConcreteDecoratorA : public Decorator
{
public:
ConcreteDecoratorA(Component *pComponent) : Decorator(pComponent) {}
void Operation()
{
Decorator::Operation(); //执行原有职责
AddOperation(); //添加职责
}
void AddOperation()
{
cout << "This is Added OperationA." << endl;
}
};
class ConcreteDecoratorB : public Decorator
{
public:
ConcreteDecoratorB(Component *pComponent) : Decorator(pComponent) {}
void Operation()
{
Decorator::Operation(); //执行原有职责
AddOperation(); //添加职责
}
void AddOperation()
{
cout << "This is Added OperationB." << endl;
}
};
int main(void)
{
Component *pConcreteComponent = new ConcreteComponent();
Decorator *pConcreteDecoratorA = new ConcreteDecoratorA(pConcreteComponent);
pConcreteDecoratorA->Operation();
cout << "============================" << endl;
Decorator *pConcreteDecoratorB = new ConcreteDecoratorB(pConcreteComponent);
pConcreteDecoratorB->Operation();
return 0;
}
运行结果:
八、Bridge Mode 桥接模式
依赖倒置原则、开放封闭原则
将抽象部分与它的具体实现解耦,使得二者可以独立的变化。桥接模式将两个独立变化的部分(即抽象接口与具体实现)解耦开,在抽象层建立两者的关联关系。
- Abstraction(抽象类):定义抽象类的接口,维护一个指向Implementor接口的指针。
- RefineAbstraction(扩充抽象类):实现抽象类接口,可以直接调用Implementor接口的TurnOn方法;
- Implementor(实现类接口):定义实现类的接口。只定义接口,实现细节由具体实现类去完成,需要不同的实现时只需要修改具体实现类即可,接口不用修改(开放封闭原则)。
- ConcreteImplementor(具体实现类):具体实现Implementor接口。不同的具体实现类拥有不同的操作。
类图右边部分,具体实现类是对接口的实现,而对外只提供调用的接口,将实现细节隐藏起来。
类图左边部分,抽象类维护了一个接口指针,因此抽象类和接口类是单向关联的关系。在抽象中引用实现部分,只需调用实现类接口,而不用考虑具体的实现细节,这样就将抽象部分和它的实现部分解耦开了。
简言之,在Abstraction类中维护一个Implementor类指针,需要采用不同的实现方式的时候只需要传入不同的Implementor派生类就可以了。
实例代码:
#include <iostream>
using namespace std;
//实现类接口,定义了一个接口
class Implementor
{
public:
virtual void TurnOn() = 0;
};
//具体实现类A、B:具体实现Implementor接口
class ConcreteImplementor_A : public Implementor
{
void TurnOn()
{
cout << "Turn On the living room lights"<< endl;
}
};
class ConcreteImplementor_B : public Implementor
{
void TurnOn()
{
cout << "Turn On the bedroom lights"<< endl;
}
};
//抽象类:定义了一个接口
class Abstraction
{
public:
Abstraction(Implementor *pImpl) : m_Implementor(pImpl) {}
virtual void TurnOnTheLights() = 0;
protected:
Implementor *m_Implementor;
};
//扩充抽象类:实现了抽象类的接口
class RefinedAbstraction : public Abstraction
{
public:
RefinedAbstraction(Implementor *pImpl) : Abstraction(pImpl) {}
virtual void TurnOnTheLights()
{
m_Implementor->TurnOn();
}
};
int main(int argc, char **argv)
{
Implementor * pImpl_A = new ConcreteImplementor_A();
Abstraction * pRefAbstr_A = new RefinedAbstraction(pImpl_A);
pRefAbstr_A->TurnOnTheLights(); //打开客厅的灯
Implementor * pImpl_B = new ConcreteImplementor_B();
Abstraction * pRefAbstr_B = new RefinedAbstraction(pImpl_B);
pRefAbstr_B->TurnOnTheLights(); //打开卧室的灯
}
运行结果:
九、单例模式
确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
单例模式结构很简单,这里以打印机为例,假设只有一台打印机,打印机只能同时打印一份文档。
首先,为确保只有一个实例,防止创建多个对象,必须将构造函数设为私有的,只能通过类的静态方法实例化一个对象。其次,为了提供一个全局访问点来访问这个唯一实例,Printer类提供了公有的、静态的GetInstance方法,调用该方法即返回该实例。
9.1 简单的单例模式:
实例代码:
#include <iostream>
using namespace std;
class Printer
{
public:
static Printer *GetInstance()
{
if (NULL == m_instance)
{
cout << "Create a printer." << endl;
m_instance = new Printer();
}
return m_instance;
}
void work()
{
cout << "The printer is printing a document" << endl;
}
private:
Printer() {}
static Printer *m_instance;
};
Printer* Printer::m_instance = NULL;
int main(int argc, char **argv)
{
Printer *MyPrinter = Printer::GetInstance();
MyPrinter->work();
Printer *MyPrinter_c = Printer::GetInstance();
MyPrinter_c->work();
}
运行结果:
可以看到,多次调用GetInstance方法也只有在第一次调用时实例化对象,以后调用直接返回该实例的指针。
9.2 线程安全的单例模式
上述代码是单例模式最简单的使用,但不是线程安全的。如果有多个线程企图调用GetInstance方法访问该实例,则存在创建多个实例的风险。如何做到线程安全?有很多种方法,最简单的就是在GetInstance中加锁。
实例代码:
#include <iostream>
#include <mutex>
using namespace std;
class Printer
{
public:
static Printer *GetInstance()
{
if (NULL == m_instance)
{
m_mutex.lock();
if (NULL == m_instance)
{
cout << "Create a printer." << endl;
m_instance = new Printer();
}
m_mutex.unlock();
}
return m_instance;
}
void work()
{
cout << "The printer is printing a document" << endl;
}
private:
Printer() {}
static Printer *m_instance;
static mutex m_mutex;
};
Printer* Printer::m_instance = NULL;
mutex Printer::m_mutex;
int main(int argc, char **argv)
{
Printer *MyPrinter = Printer::GetInstance();
MyPrinter->work();
Printer *MyPrinter_c = Printer::GetInstance();
MyPrinter_c->work();
}
此处进行了两次m_Instance == NULL的判断,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。
9.3 饿汉实现
单例有两种实现方法:懒汉与饿汉。
- 懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上面的方法属于懒汉实现;
- 饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。
特点与选择:
- 由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
- 在访问量较小时,采用懒汉实现。这是以时间换空间。
实例代码:
#include <iostream>
#include <mutex>
using namespace std;
class Printer
{
public:
static Printer *GetInstance()
{
return m_instance;
}
void work()
{
cout << "The printer is printing a document" << endl;
}
private:
Printer() {}
static Printer *m_instance;
};
Printer* Printer::m_instance = new Printer;
int main(int argc, char **argv)
{
Printer *MyPrinter = Printer::GetInstance();
MyPrinter->work();
}
由于m_instance是静态成员变量,所以在程序运行时就会实例化一个对象出来,当调用GetInstance方法时,直接返回该实例即可。饿汉是线程安全的,因此不用加锁。