设计模式(二)23种设计模式

文章目录

  • 设计模式(二)23种设计模式
    • 组件协作模式
      • 策略模式
      • 观察者模式
    • 单一职责模式
      • Decorator模式
      • Bridge模式
    • 对象创建模式
      • Factory Method
      • Abstract Factory
      • Prototype原型模式
      • builder模式
    • 对象性能模式
      • Singleton单例模式
      • Flyweight享元模式
    • 接口隔离模式
      • Facade(法语)门面模式
      • Proxy代理模式
      • Adapter适配器
      • mediator中介者模式
    • 状态变化模式
      • State模式
      • Memento备忘录模式
    • 状态变化模式
      • Composite模式
      • Iterator迭代器
      • Chain of Responsibility 职责链
    • 行为变化模式
      • Command命令模式
      • Visitor访问器模式
    • 领域规则模式
      • Interpreter解析器模式
    • 设计模式总结

菜鸟教程设计模式

设计模式面试题速成版

本篇文章中的引用皆来自于《设计模式》GoF

组件协作模式

策略模式

  • 要有“时间轴”概念

动机

  1. 支持经常改变
  2. 透明的更改算法,使得算法和对象本身解耦

定义

定义一系列算法,把它们封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)

理解

//实现需求---->税法策略,添加一个国家的策略
//遵循开放封闭原则,满足复用性
class TaxStrategy{
public:
    virtual double Calculate(const Context& context)=0;
    virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};
class USTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};
class DETax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

//扩展
//*********************************
class FRTax : public TaxStrategy{
public:
	virtual double Calculate(const Context& context){
		//.........
	}
};

class SalesOrder{
private:
    TaxStrategy* strategy;

public:
    SalesOrder(StrategyFactory* strategyFactory){
        this->strategy = strategyFactory->NewStrategy();//运行时
    }
    ~SalesOrder(){
        delete this->strategy;
    }

    public double CalculateTax(){
        //...
        Context context();
        
        double val = 
            strategy->Calculate(context); //多态调用
        //...
    }
    
};
  • 可重用性提高
  • 提供了除了条件判断语句以外的另一种选择,消除条件判断语句,解耦合
  • 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销

观察者模式

动机

  1. 在软件构建过程中,需要建立“通知依赖关系”——一个对象的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好的抵御变化
  2. 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

定义

定义对象间的一种一对多(变化)的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新

理解

//设计场景-->设计大文件传输进度条
//MainForm.cpp
class MainForm : public Form, public IProgress//支持多继承
{
	TextBox* txtFilePath;
	TextBox* txtFileNumber;

	ProgressBar* progressBar;

public:
	void Button1_Click(){

		string filePath = txtFilePath->getText();
		int number = atoi(txtFileNumber->getText().c_str());

		ConsoleNotifier cn;

		FileSplitter splitter(filePath, number);

		splitter.addIProgress(this); //订阅通知
		splitter.addIProgress(&cn); //订阅通知

		splitter.split();

		splitter.removeIProgress(this);

	}

	virtual void DoProgress(float value){
		progressBar->setValue(value);
	}
};

class ConsoleNotifier : public IProgress {
public:
	virtual void DoProgress(float value){
		cout << ".";
	}
};
//FileSplitter.cpp
class IProgress{//具体的细节实现
public:
	virtual void DoProgress(float value)=0;
	virtual ~IProgress(){}
};
//以上在上面mainform文件实现
//进度通知的抽象接口

class FileSplitter
{
	string m_filePath;
	int m_fileNumber;

	List<IProgress*>  m_iprogressList; // 抽象通知机制,支持多个观察者(多个多个!!!)
	
public:
	FileSplitter(const string& filePath, int fileNumber) :
		m_filePath(filePath), 
		m_fileNumber(fileNumber){

	}


	void split(){

		//1.读取大文件

		//2.分批次向小文件中写入
		for (int i = 0; i < m_fileNumber; i++){
			//...

			float progressValue = m_fileNumber;
			progressValue = (i + 1) / progressValue;
			onProgress(progressValue);//发送通知
		}

	}


	void addIProgress(IProgress* iprogress){
		m_iprogressList.push_back(iprogress);
	}

	void removeIProgress(IProgress* iprogress){
		m_iprogressList.remove(iprogress);
	}


protected:
	virtual void onProgress(float value){
		
		List<IProgress*>::iterator itor=m_iprogressList.begin();

		while (itor != m_iprogressList.end() )
			(*itor)->DoProgress(value); //更新进度条
			itor++;
		}
	}
};

单一职责模式

Decorator模式

动机

  1. 解决”过度的使用继承来扩展对象的功能“,且由于继承引入的静态特质,使得该扩展方式缺乏灵活性,并且随着子类增多,各种子类的组合会导致更多的子类膨胀的问题。
  2. 将”对象功能扩展“根据需要动态实现

定义

动态(组合)地给一个对象增加一些额外的职责,就增加功能而言,装饰模式比生成子类(继承)更为灵活(消除重读代码&减少子类个数)

理解

应对有“每一步都需要加密操作“的场景

节省规模

//业务操作
class Stream{

public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;
    
    virtual ~Stream(){}
};

//主体类
class FileStream: public Stream{
public:
    virtual char Read(int number){
        //读文件流
    }
    virtual void Seek(int position){
        //定位文件流
    }
    virtual void Write(char data){
        //写文件流
    }

};

class NetworkStream :public Stream{
public:
    virtual char Read(int number){
        //读网络流
    }
    virtual void Seek(int position){
        //定位网络流
    }
    virtual void Write(char data){
        //写网络流
    }
    
};

class MemoryStream :public Stream{
public:
    virtual char Read(int number){
        //读内存流
    }
    virtual void Seek(int position){
        //定位内存流
    }
    virtual void Write(char data){
        //写内存流
    }
    
};

//扩展操作

DecoratorStream: public Stream{
protected:
    Stream* stream;//...
    
    DecoratorStream(Stream * stm):stream(stm){
    
    }
    
};

class CryptoStream: public DecoratorStream {
 

public:
    CryptoStream(Stream* stm):DecoratorStream(stm){
    
    }
    
    
    virtual char Read(int number){
       
        //额外的加密操作...
        stream->Read(number);//读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作...
        stream::Seek(position);//定位文件流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        stream::Write(data);//写文件流
        //额外的加密操作...
    }
};



class BufferedStream : public DecoratorStream{
    
    Stream* stream;//...
    
public:
    BufferedStream(Stream* stm):DecoratorStream(stm){
        
    }
    //...
};




void Process(){

    //运行时装配
    FileStream* s1=new FileStream();
    
    CryptoStream* s2=new CryptoStream(s1);
    
    BufferedStream* s3=new BufferedStream(s1);
    
    BufferedStream* s4=new BufferedStream(s2);
    
    

}

Bridge模式

动机

  1. 由于某些类型地固有地实现逻辑,使得它们具有两个及以上个维度的变化
  2. 解决应对”多维度变化“的问题

定义

将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立变化

理解

”合并同类项“

//简单的通讯实现
//对比原来的一个功能实现一个类(全是细节,没有抽象)的臃肿状态,现在显得精炼了很多
class Messager{
protected:
     MessagerImp* messagerImp;//...
public:
    virtual void Login(string username, string password)=0;
    virtual void SendMessage(string message)=0;
    virtual void SendPicture(Image image)=0;
    
    virtual ~Messager(){}
};

class MessagerImp{
public:
    virtual void PlaySound()=0;
    virtual void DrawShape()=0;
    virtual void WriteText()=0;
    virtual void Connect()=0;
    
    virtual MessagerImp(){}
};


//平台实现 n
class PCMessagerImp : public MessagerImp{
public:
    
    virtual void PlaySound(){
        //**********
    }
    virtual void DrawShape(){
        //**********
    }
    virtual void WriteText(){
        //**********
    }
    virtual void Connect(){
        //**********
    }
};

class MobileMessagerImp : public MessagerImp{
public:
    
    virtual void PlaySound(){
        //==========
    }
    virtual void DrawShape(){
        //==========
    }
    virtual void WriteText(){
        //==========
    }
    virtual void Connect(){
        //==========
    }
};



//业务抽象 m

//类的数目:1+n+m

class MessagerLite :public Messager {

    
public:
    
    virtual void Login(string username, string password){
        
        messagerImp->Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        messagerImp->WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        messagerImp->DrawShape();
        //........
    }
};



class MessagerPerfect  :public Messager {
public:
    
    virtual void Login(string username, string password){
        
        messagerImp->PlaySound();
        //********
        messagerImp->Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        messagerImp->PlaySound();
        //********
        messagerImp->WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        messagerImp->PlaySound();
        //********
        messagerImp->DrawShape();
        //........
    }
};

void Process(){
    //运行时装配
    MessagerImp* mImp=new PCMessagerImp();
    Messager *m =new Messager(mImp);
}

对象创建模式

通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。

Factory Method

动机

  1. 在软件系统中,经常面临着创建对象的工作;由于需求的变化, 需要创建的对象的具体类型经常变化。
  2. 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一 种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧 耦合

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。 Factory Method使得一个类的实例化延迟(目的:解耦, 手段:虚函数)到子类。

理解

//问题
//如文件分割问题
//建立一个抽象类
class ISplitter{
    public:
    virtual void split()=0;
    virtual ~ISplitter(){};
};
//下面基于抽象类实现一个具体的类
class BinarySplitter : public ISplitter{
    
};
//在总类中这样调用,通过new导致了本质上实现还是依赖于binaryS这个具体的类
ISplitter * splitter= new BinarySplitter;
//解决办法
//new是编译式依赖,virtual实现运行时依赖
//添加工厂基类
class SplitterFactory{
    public:
   	virtual ISplitter* CreateSplitter()=0;
    virtual ~SplitterFactory(){};
}

//在总类中调用
class MainForm : public Form{
    SplitterFactory* factory;//工厂
    public:
    MainForm(SplitterFactory* factory){
        this->factory=factory;
    }//mainform类的构造函数,具体函数类型在构造的时候被外界传进来
    void Button_Click(){
    ISplitter * splitter= factory->CreateSplitter();//多态new       splitter->split();
    }
}

//添加具体类工厂
class BinarySlpitterFactory: pubilc SlitterFactory{
  public:
  virtual ISplitter* CreateSplitter(){
      return new BinarySplitter();
  }
};

//本质上是将”变化“赶出去,关在某个局部的地方
//现在可以只加子类和子类工厂就可以了
//缺点在于要求创建方法/参数相同

Abstract Factory

动机

  1. 在软件系统中,经常面临着“一系列相互依赖的对象”的创建工 作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
  2. 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一 种“封装机制”来避免客户程序和这种“多系列具体对象创建工作” 的紧耦合?

定义

提供一个接口,让该接口负责创建一系列“相关或者相互依 赖的对象”,无需指定它们具体的类。

理解

//问题
class EmployeeDAO{
public:
    vector<EmployeeDO> GetEmployees(){
        SqlConnection* connection =
            new SqlConnection();
        connection->ConnectionString = "...";

        SqlCommand* command =
            new SqlCommand();
        command->CommandText="...";
        command->SetConnection(connection);

        SqlDataReader* reader = command->ExecuteReader();
        while (reader->Read()){

        }
    }
};
//不方便改动,不稳定

//数据库访问有关的基类
class IDBConnection{
    
};

class IDBCommand{
    
};

class IDataReader{
    
};

//将原来的三个工厂基类合在一起
class IDBFactory{
public:
    virtual IDBConnection* CreateDBConnection()=0;
    virtual IDBCommand* CreateDBCommand()=0;
    virtual IDataReader* CreateDataReader()=0;
    
};

//支持SQL Server
class SqlConnection: public IDBConnection{
    
};
class SqlCommand: public IDBCommand{
    
};
class SqlDataReader: public IDataReader{
    
};

class SqlDBFactory:public IDBFactory{
public:
    virtual IDBConnection* CreateDBConnection()=0;
    virtual IDBCommand* CreateDBCommand()=0;
    virtual IDataReader* CreateDataReader()=0;
 
};

//支持Oracle
class OracleConnection: public IDBConnection{
    
};

class OracleCommand: public IDBCommand{
    
};

class OracleDataReader: public IDataReader{
    
};

class EmployeeDAO{
    IDBFactory* dbFactory;
    
public:
    vector<EmployeeDO> GetEmployees(){
        IDBConnection* connection =
            dbFactory->CreateDBConnection();
        connection->ConnectionString("...");

        IDBCommand* command =
            dbFactory->CreateDBCommand();
        command->CommandText("...");
        command->SetConnection(connection); //关联性

        IDBDataReader* reader = command->ExecuteReader(); //关联性
        while (reader->Read()){

        }

    }
};

  • 如果没有应对“多系列对象构建”的需求变化,则没有必要使用 Abstract Factory模式,这时候使用简单的工厂完全可以

  • “系列对象”指的是在某一特定系列下的对象之间有相互依赖、 或作用的关系。不同系列的对象之间不能相互依赖。

  • Abstract Factory模式主要在于应对“新系列”的需求变动。其缺 点在于难以应对“新对象”的需求变动。

Prototype原型模式

动机

  1. 在软件系统种,经常面临着”某些结构复杂的对象“的创建工作,由于需求的变化,这些对象经常面临着聚类的变化,但是它们却拥有比较稳定一致的接口
  2. 如何应对这种变化?如何想客户程序”隔离出“这些易变对象,从而使得”依赖这些易变对象的客户程序“不随着需求改变而改变

定义

使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象

理解

//对于工厂模式对应情景
//将抽象类和抽象基类”合并“
//抽象类
class ISplitter{
    public:
    virtual void split()=0;
    virtual ISplitter* clone()=0;
    virtual ~ISplitter(){}
}; 
//具体类
class BinarySplitter: public ISplitter{
    public:
    virtual ISplitter* clone(){
        return new BinarySplitter(*this);
    }
};
//MainForm
class MainForm : public Form{
    ISplitter* prototype;//原型对象
    public:
    MainForm(ISplitter* prototype){
        this->prototype=prototype;
    }
    //原型对象不能直接拿来用,原型对象是用来clone的
    void Button_Click(){
    ISplitter * splitter= prototype->clone();//克隆原型
    splitter->split();
    }
}

重点是clone,有些时候利用框架中的序列化来实现深拷贝

builder模式

较为小众

动机

  1. 在软件系统中,有时候面临着“一个复杂对象”的创建工作,其 通常由各个部分的子对象用一定的算法构成;由于需求的变化,这 个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在 一起的算法却相对稳定
  2. 如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂 对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不 随着需求改变而改变?

定义

将一个复杂对象的构建与其表示相分离,使得同样的构建过 程(稳定)可以创建不同的表示(变化)

理解

//问题
//游戏场景中建房子
//将house和housebuilder分开
class House{//抽象类
    //....
};

class HouseBuilder {//抽象类
public:
    House* GetResult(){
        return pHouse;
    }
    virtual ~HouseBuilder(){}
protected:
    
    House* pHouse;
	virtual void BuildPart1()=0;
    virtual void BuildPart2()=0;
    virtual void BuildPart3()=0;
    virtual void BuildPart4()=0;
    virtual void BuildPart5()=0;
	
};

class StoneHouse: public House{
    
};

class StoneHouseBuilder: public HouseBuilder{
protected:
    
    virtual void BuildPart1(){
        //pHouse->Part1 = ...;
    }
    virtual void BuildPart2(){
        
    }
    virtual void BuildPart3(){
        
    }
    virtual void BuildPart4(){
        
    }
    virtual void BuildPart5(){
        
    }
    
};

//一个类不要太”肥“
class HouseDirector{
    
public:
    HouseBuilder* pHouseBuilder;
    
    HouseDirector(HouseBuilder* pHouseBuilder){
        this->pHouseBuilder=pHouseBuilder;
    }
    
    House* Construct(){
        
        pHouseBuilder->BuildPart1();
        
        for (int i = 0; i < 4; i++){
            pHouseBuilder->BuildPart2();
        }
        
        bool flag=pHouseBuilder->BuildPart3();
        
        if(flag){
            pHouseBuilder->BuildPart4();
        }
        
        pHouseBuilder->BuildPart5();
        
        return pHouseBuilder->GetResult();
    }
};
  • 在Builder模式中,要注意不同语言中构造器内调用虚函数的差别 (C++ vs. C#) C++的类构造的时候不能直接调用虚函数,C#可以

对象性能模式

Singleton单例模式

动机

  1. 在软件系统中,经常特殊类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性及良好的效率
  2. 这应该是类设计者的责任,而不是使用者的责任

定义

保证一个类仅有一个实例,并提供一个该实例的全局访问点

理解

class Singleton{
private:
    Singleton();
    Singleton(const Singleton& other);
public:
    static Singleton* getInstance();
    static Singleton* m_instance;
};

Singleton* Singleton::m_instance=nullptr;

//线程非安全版本
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

//双检查锁,但由于内存读写reorder不安全,会导致双检查锁的失效
Singleton* Singleton::getInstance() {
    
    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {//判断是否为空
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

//解决reorder问题,编译器方面需要优化
//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

Flyweight享元模式

动机

  1. 在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价——主要指内存需求方面的代价

  2. 避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明的使用面向对象的方式来进行操作

定义

运用共享技术有效地支持大量细粒度的对象

理解

class Font {
private:
    //unique object key
    string key;
    //object state
    //....
    
public:
    Font(const string& key){
        //...
    }
};

class FontFactory{
private:
    map<string,Font* > fontPool;
    
public:
    Font* GetFont(const string& key){

        map<string,Font*>::iterator item=fontPool.find(key);
        
        if(item!=footPool.end()){
            return fontPool[key];
        }
        else{
            Font* font = new Font(key);
            fontPool[key]= font;
            return font;
        }

    }
    
    void clear(){
        //...
    }
};

接口隔离模式

采用添加一层间接接口来解决问题

Facade(法语)门面模式

动机

给他加一个”门面“,来总理隔离内容,外界客户的一切要求通过门面来接触到内在

定义

为子系统中的一组接口提供一个一致(稳定)的界面,门面模式定义了一个高层接口,这个接口使得着一子系统更加容易使用(复用)。

理解

  • 简化系统接口,对组件内部与外部客户程序来说,达到一种”解耦“的效果——内部子系统的任何变化不会影响到门面接口的变化

  • 门面设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。门面更多的时候是一种架构设计模式

  • 门面设计模式不是一个集装箱,可以任意放进任何多个对象。门面模式中的组件的内部应该是”相互耦合关系比较大的一些列组件“,而不是一个见到那的功能集合

Proxy代理模式

动机

  1. 在面向对象系统种,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者,或者系统结构带来很多麻烦。
  2. 在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性,增加一层间接层是软件开发中常见的解决方式

定义

为其他对象提供一种代理控制(隔离,使用接口)对这个对象的访问。

理解

//问题
class ISubject{
    public:
	virtual void process();
};

class RealSubject: public ISubject{
   	public:
    virtual void process(){
        //...
    }
};

class ClientApp{
    ISubject* subject;
    public:
    ClientApp(){
        subject=new RealSubject();//无法直接生成
    }
    void DoTack(){
        //...
        subject->process();
        //...
    }
};
//解决方法
class ISubject{
    public:
	virtual void process();
};

//proxy的 一种表示
class SubjectProxy: public ISubject{
   	public:
    virtual void process(){
        //...对RealSubject的一种简介访问
    }
};

class ClientApp{
    ISubject* subject;
    public:
    ClientApp(){
        subject=new SubjectProxy();
    }
    void DoTack(){
        //...
        subject->process();
        //...
    }
};
  • 具体的设计模式实现方法,实现粒度相差很大,有些可能对单个对象做细粒度的控制
  • 本质上是一种间接方法

Adapter适配器

C++ 标准库中有大量应用

动机

  1. 由于应用环境的变化,有时候需要将”一些现存的对象“放在新的环境中应用,但是新环境要求的接口是现存对象所不满足的
  2. 应对”迁移的变化“

定义

将一个类的接口转换成客户希望的另一个接口,Adapter模式使得原本对于接口不兼容而不能一起工作的那些类可以一起工作

理解

//目标接口(新接口)
class ITarget{
public:
    virtual void process()=0;
};

//遗留接口(老接口)
class IAdaptee{
public:
    virtual void foo(int data)=0;
    virtual int bar()=0;
};

//遗留类型
class OldClass: public IAdaptee{
    //....
};

//对象适配器
class Adapter: public ITarget{ //继承
protected:
    IAdaptee* pAdaptee;//组合
    
public:
    
    Adapter(IAdaptee* pAdaptee){
        this->pAdaptee=pAdaptee;
    }
    
    virtual void process(){
        int data=pAdaptee->bar();
        pAdaptee->foo(data);
        
    }
    
    
};

//类适配器
class Adapter: public ITarget,
               protected OldClass{ //多继承
               
               
}
int main(){
    IAdaptee* pAdaptee=new OldClass();
    
    
    ITarget* pTarget=new Adapter(pAdaptee);
    pTarget->process();
    
    
}
class stack{
    deqeue container;
    
};

class queue{
    deqeue container;
    
};
  • ”希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”
  • 对象适配器和类适配器。但是类适配器采用“多继承”实现方式,一般不推荐使用,不灵活
  • adapter模式可以实现的非常灵活,不必拘泥于Cof23中定义的两种接口。

mediator中介者模式

动机

解决多个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的应用关系将面临不断的变化。使用一个中介对象来做“间接”

定义

用一个中介对象来封装(封装变化)一系列的对象交互,中介者使个对象不需要显示的互相引用(编译时依赖->运行时依赖),从而使其松散耦合(管理变化),而且可以独立的改变它们之间的交互。

理解

对多个对象间复杂关联关系解耦

状态变化模式

在组件构建中,某些对象的状态经常面对变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定?状态变化模式为这一问题提供了一种解决方案

State模式

  1. 在软件构建过程中,某些对象的状态如果改变,其行为也会随之发生变化,比如文档指出与只读状态,其支持的行为和读写状态支持的行为就可能完全不同。
  2. 如何在运行时根据对象的状态来透明的更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?

定义

允许一个对象在其内部状态改变时改变它的行为,从而使对象看起来似乎修改了其行为。

理解

//问题
//网络场景
enum NetworkState
{
    Network_Open,
    Network_Close,
    Network_Connect,
};

class NetworkProcessor{
    
    NetworkState state;

public:
    
    void Operation1(){
        if (state == Network_Open){

            //**********
            state = Network_Close;
        }
        else if (state == Network_Close){

            //..........
            state = Network_Connect;
        }
        else if (state == Network_Connect){

            //$$$$$$$$$$
            state = Network_Open;
        }
    }

    public void Operation2(){

        if (state == Network_Open){
            
            //**********
            state = Network_Connect;
        }
        else if (state == Network_Close){

            //.....
            state = Network_Open;
        }
        else if (state == Network_Connect){

            //$$$$$$$$$$
            state = Network_Close;
        }
    
    }

    public void Operation3(){

    }
};
//解决方法
class NetworkState{

public:
    NetworkState* pNext;
    virtual void Operation1()=0;
    virtual void Operation2()=0;
    virtual void Operation3()=0;

    virtual ~NetworkState(){}
};


class OpenState :public NetworkState{
    
    static NetworkState* m_instance;
public:
    static NetworkState* getInstance(){
        if (m_instance == nullptr) {
            m_instance = new OpenState();
        }
        return m_instance;
    }

    void Operation1(){
        
        //**********
        pNext = CloseState::getInstance();
    }
    
    void Operation2(){
        
        //..........
        pNext = ConnectState::getInstance();
    }
    
    void Operation3(){
        
        //$$$$$$$$$$
        pNext = OpenState::getInstance();
    }
    
    
};

class CloseState:public NetworkState{ }
//...
//扩展方法

class NetworkProcessor{
    
    NetworkState* pState;
    
public:
    
    NetworkProcessor(NetworkState* pState){
        
        this->pState = pState;
    }
    
    void Operation1(){
        //...
        pState->Operation1();
        pState = pState->pNext;
        //...
    }
    
    void Operation2(){
        //...
        pState->Operation2();
        pState = pState->pNext;
        //...
    }
    
    void Operation3(){
        //...
        pState->Operation3();
        pState = pState->pNext;
        //...
    }

};
  • State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象,但同时维持State的接口,这样实现了具体操作与状态转换之间的解耦
  • 为不同的状态引入不同的对象使得状态转化变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的——即要么彻底转换,要么不转换。
  • 如果State对象没有实例变量,那么各个上下文可以共享一个State对象,从而节省对象开销。

Memento备忘录模式

动机

某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。

如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。

定义

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。

理解


class Memento
{
    string state;
    //..
public:
    Memento(const string & s) : state(s) {}
    string getState() const { return state; }
    void setState(const string & s) { state = s; }
};



class Originator
{
    string state;
    //....
public:
    Originator() {}
    Memento createMomento() {
        Memento m(state);
        return m;
    }
    void setMomento(const Memento & m) {
        state = m.getState();
    }
};



int main()
{
    Originator orginator;
    
    //捕获对象状态,存储到备忘录
    Memento mem = orginator.createMomento();
    
    //... 改变orginator状态
    
    //从备忘录中恢复
    orginator.setMomento(memento);

   
    
}
  • 相当于,在某些状态下拍下一个快照
  • 今天来讲有点过时,现代语言运行时,C#和JAVA等,都具有相当的序列对象化支持,因此往往采用效率最高,又较容易实现的序列化方案来实现Memento模式
  • Memento模式的核心时信息隐藏,即Originator需要向对外接隐藏性息,保持其封装性,但同时又需要将状态保持到外界
  • 其实不必将其套作一个“设计模式”大帽子现在

状态变化模式

常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,极大地破坏了组件的复用。这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据无关的访问,是一种行之有效的解决方案。

Composite模式

动机

在软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码频繁变化,带来了代码维护性,扩展性等弊端

定义

将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)

理解

#include <iostream>
#include <list>
#include <string>
#include <algorithm>

using namespace std;

class Component
{
public:
    virtual void process() = 0;
    virtual ~Component(){}
};

//树节点
class Composite : public Component{
    
    string name;
    list<Component*> elements;
public:
    Composite(const string & s) : name(s) {}
    
    void add(Component* element) {
        elements.push_back(element);
    }
    void remove(Component* element){
        elements.remove(element);
    }
    
    void process(){
        
        //1. process current node
        //2. process leaf nodes
        for (auto &e : elements)
            e->process(); //多态调用
         
    }
};

//叶子节点
class Leaf : public Component{
    string name;
public:
    Leaf(string s) : name(s) {}
            
    void process(){
        //process current node
    }
};


void Invoke(Component & c){
    //...
    c.process();
    //...
}


int main()
{

    Composite root("root");
    Composite treeNode1("treeNode1");
    Composite treeNode2("treeNode2");
    Composite treeNode3("treeNode3");
    Composite treeNode4("treeNode4");
    Leaf leat1("left1");
    Leaf leat2("left2");
    
    root.add(&treeNode1);
    treeNode1.add(&treeNode2);
    treeNode2.add(&leaf1);
    
    root.add(&treeNode3);
    treeNode3.add(&treeNode4);
    treeNode4.add(&leaf2);
    
    process(root);
    process(leaf2);
    process(treeNode3);
  
}
  • 将一对多转化为一对一关系
  • 客户代码将与存粹的抽象接口发生以来
  • 反向追溯,如果父对象有频繁遍历需求,可以显著提高效率

Iterator迭代器

动机

在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明的访问其中包含的元素,同时这种透明遍历也对同一种算法在多种集合对象上进行操作提供了可能。

定义

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示

理解

template<typename T>
class Iterator
{
public:
    virtual void first() = 0;
    virtual void next() = 0;
    virtual bool isDone() const = 0;
    virtual T& current() = 0;
};

template<typename T>
class MyCollection{
    
public:
    
    Iterator<T> GetIterator(){
        //...
    }
    
};

template<typename T>
class CollectionIterator : public Iterator<T>{
    MyCollection<T> mc;
public:
    
    CollectionIterator(const MyCollection<T> & c): mc(c){ }
    
    void first() override {
        
    }
    void next() override {
        
    }
    bool isDone() const override{
        
    }
    T& current() override{
        
    }
};

void MyAlgorithm()
{
    MyCollection<int> mc;
    
    Iterator<int> iter= mc.GetIterator();
    
    for (iter.first(); !iter.isDone(); iter.next()){
        cout << iter.current() << endl;
    }
}

这里定义的迭代器有些过时,与现有语言标准库中的有设计

Chain of Responsibility 职责链

动机

一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显式指定,将被必不可少的带来请求发送者与接收者的紧耦合。

定义

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

理解

#include <iostream>
#include <string>

using namespace std;

enum class RequestType
{
    REQ_HANDLER1,
    REQ_HANDLER2,
    REQ_HANDLER3
};

class Reqest
{
    string description;
    RequestType reqType;
public:
    Reqest(const string & desc, RequestType type) : description(desc), reqType(type) {}
    RequestType getReqType() const { return reqType; }
    const string& getDescription() const { return description; }
};

class ChainHandler{
    
    ChainHandler *nextChain;//多态指针
    void sendReqestToNextHandler(const Reqest & req)
    {
        if (nextChain != nullptr)
            nextChain->handle(req);
    }
protected:
    virtual bool canHandleRequest(const Reqest & req) = 0;
    virtual void processRequest(const Reqest & req) = 0;
public:
    ChainHandler() { nextChain = nullptr; }
    void setNextChain(ChainHandler *next) { nextChain = next; }
    
   
    void handle(const Reqest & req)
    {
        if (canHandleRequest(req))
            processRequest(req);
        else
            sendReqestToNextHandler(req);
    }
};


class Handler1 : public ChainHandler{
protected:
    bool canHandleRequest(const Reqest & req) override
    {
        return req.getReqType() == RequestType::REQ_HANDLER1;
    }
    void processRequest(const Reqest & req) override
    {
        cout << "Handler1 is handle reqest: " << req.getDescription() << endl;
    }
};
        
class Handler2 : public ChainHandler{
protected:
    bool canHandleRequest(const Reqest & req) override
    {
        return req.getReqType() == RequestType::REQ_HANDLER2;
    }
    void processRequest(const Reqest & req) override
    {
        cout << "Handler2 is handle reqest: " << req.getDescription() << endl;
    }
};

class Handler3 : public ChainHandler{
protected:
    bool canHandleRequest(const Reqest & req) override
    {
        return req.getReqType() == RequestType::REQ_HANDLER3;
    }
    void processRequest(const Reqest & req) override
    {
        cout << "Handler3 is handle reqest: " << req.getDescription() << endl;
    }
};

int main(){
    Handler1 h1;
    Handler2 h2;
    Handler3 h3;
    h1.setNextChain(&h2);
    h2.setNextChain(&h3);
    
    Reqest req("process task ... ", RequestType::REQ_HANDLER3);
    h1.handle(req);
    return 0;
}
  • “链表”模式应用不多
  • 对象职责分派更具灵活性,可以在运行时动态添加/修改请求的处理职责

行为变化模式

组件行为经常导致组件本身剧烈的变化,行为变化模式将组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合

Command命令模式

动机

在软件构建过程中,“行为请求者”和“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行记录、撤销、重做事物等处理,这种无法抵御变化的紧耦合是不合适的。

定义

将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

理解

#include <iostream>
#include <vector>
#include <string>
using namespace std;


class Command
{
public:
    virtual void execute() = 0;
};

class ConcreteCommand1 : public Command
{
    string arg;
public:
    ConcreteCommand1(const string & a) : arg(a) {}
    void execute() override
    {
        cout<< "#1 process..."<<arg<<endl;
    }
};

class ConcreteCommand2 : public Command
{
    string arg;
public:
    ConcreteCommand2(const string & a) : arg(a) {}
    void execute() override
    {
        cout<< "#2 process..."<<arg<<endl;
    }
};
        
        
class MacroCommand : public Command
{
    vector<Command*> commands;
public:
    void addCommand(Command *c) { commands.push_back(c); }
    void execute() override
    {
        for (auto &c : commands)
        {
            c->execute();
        }
    }
};

int main()
{

    ConcreteCommand1 command1(receiver, "Arg ###");
    ConcreteCommand2 command2(receiver, "Arg $$$");
    
    MacroCommand macro;
    macro.addCommand(&command1);
    macro.addCommand(&command2);
    
    macro.execute();

}
  • command模式与C++函数对象有些类似,前者接口规范更严格,但是有性能损失,C++函数对象签名来定义行为接口规范,更灵活,性能更高。
  • 其他语言中会有应用,一种说法是,设计模式是弥补编程语言的不足,C++中已经有相关很好的设计,在使用C++的场景中就很少用到这种模式

Visitor访问器模式

动机

由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计

定义
表示一个作用于某对象结构中的各元素操作,使得可以在不改变(稳定)个元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)

理解

//问题
#include <iostream>
using namespace std;

class Visitor;
class Element
{
public:
    virtual void accept(Visitor& visitor) = 0; //第一次多态辨析

    virtual ~Element(){}
};

class ElementA : public Element
{
public:
    void accept(Visitor &visitor) override {
        visitor.visitElementA(*this);
    }
    

};

class ElementB : public Element
{
public:
    void accept(Visitor &visitor) override {
        visitor.visitElementB(*this); //第二次多态辨析
    }

};


class Visitor{
public:
    virtual void visitElementA(ElementA& element) = 0;
    virtual void visitElementB(ElementB& element) = 0;
    
    virtual ~Visitor(){}
};

//==================================

//扩展1
class Visitor1 : public Visitor{
public:
    void visitElementA(ElementA& element) override{
        cout << "Visitor1 is processing ElementA" << endl;
    }
        
    void visitElementB(ElementB& element) override{
        cout << "Visitor1 is processing ElementB" << endl;
    }
};
     
//扩展2
class Visitor2 : public Visitor{
public:
    void visitElementA(ElementA& element) override{
        cout << "Visitor2 is processing ElementA" << endl;
    }
    
    void visitElementB(ElementB& element) override{
        cout << "Visitor2 is processing ElementB" << endl;
    }
};      
int main()
{
    Visitor2 visitor;
    ElementB elementB;
    elementB.accept(visitor);// double dispatch
    
    ElementA elementA;
    elementA.accept(visitor);

    
    return 0;
}
  • 双重分发
  • 缺点:类层次结构较为确定,其中的操作却面临经常变动
  • 前提条件较为严苛,一般使用较少,使用之后需要严格按照该设计模式规定的架构

领域规则模式

在特定领域中,某些变化虽然频繁,但可以抽象为某种规则。这时候,结合特定领域,将问题抽象为语法规则,从而给出再该领域下的一般性解决方案

Interpreter解析器模式

动机

  1. 在软件构建过程中,如果某一特定领域的问题比较复杂,类似的结构不断重复出现,如果使用普遍的编程方式来实现将面临频繁的变化
  2. 在这种情况下,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。

定义

给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。

//加减运算
#include <iostream>
#include <map>
#include <stack>

using namespace std;

class Expression {
public:
    virtual int interpreter(map<char, int> var)=0;
    virtual ~Expression(){}
};

//变量表达式
class VarExpression: public Expression {
    
    char key;
    
public:
    VarExpression(const char& key)
    {
        this->key = key;
    }
    
    int interpreter(map<char, int> var) override {
        return var[key];
    }
    
};

//符号表达式
class SymbolExpression : public Expression {
    
    // 运算符左右两个参数
protected:
    Expression* left;
    Expression* right;
    
public:
    SymbolExpression( Expression* left,  Expression* right):
        left(left),right(right){
        
    }
    
};

//加法运算
class AddExpression : public SymbolExpression {
    
public:
    AddExpression(Expression* left, Expression* right):
        SymbolExpression(left,right){
        
    }
    int interpreter(map<char, int> var) override {
        return left->interpreter(var) + right->interpreter(var);
    }
    
};

//减法运算
class SubExpression : public SymbolExpression {
    
public:
    SubExpression(Expression* left, Expression* right):
        SymbolExpression(left,right){
        
    }
    int interpreter(map<char, int> var) override {
        return left->interpreter(var) - right->interpreter(var);
    }
    
};



Expression*  analyse(string expStr) {
    
    stack<Expression*> expStack;
    Expression* left = nullptr;
    Expression* right = nullptr;
    for(int i=0; i<expStr.size(); i++)
    {
        switch(expStr[i])
        {
            case '+':
                // 加法运算
                left = expStack.top();
                right = new VarExpression(expStr[++i]);
                expStack.push(new AddExpression(left, right));
                break;
            case '-':
                // 减法运算
                left = expStack.top();
                right = new VarExpression(expStr[++i]);
                expStack.push(new SubExpression(left, right));
                break;
            default:
                // 变量表达式
                expStack.push(new VarExpression(expStr[i]));
        }
    }
   
    Expression* expression = expStack.top();

    return expression;
}

void release(Expression* expression){
    
    //释放表达式树的节点内存...
}

int main(int argc, const char * argv[]) {
    
    
    string expStr = "a+b-c+d-e";//简单的加减运算
    map<char, int> var;
    var.insert(make_pair('a',5));
    var.insert(make_pair('b',2));
    var.insert(make_pair('c',1));
    var.insert(make_pair('d',6));
    var.insert(make_pair('e',10));

    
    Expression* expression= analyse(expStr);
    
    int result=expression->interpreter(var);
    
    cout<<result<<endl;
    
    release(expression);
    
    return 0;
}

  • 满足的业务场景——“业务规则变化频繁,且类似结构不断重复出现,并且容易抽象为语法规则的问题”
  • 使用解析器模式来表示文法规则,从而可以使用面向对象技巧来方便的扩展文法
  • 解析器模式比较适合简单的文法表示,对于复杂文法的表示,解析器模式会产生比较大的类层析机构,需要求助于语法分析生成器这样的标准工具。
  • 今日当善用其设计思想,设计模式本身应用有限

设计模式总结

  1. 一个目标:管理变化,提高复用

  2. 两种手段:分解vs抽象

  3. 八大原则:DIP,OCP,SRP,LSP,ISP,对象组合由于类继承,封装变化点,面向接口编程

  4. 重构技法:静态—>动态,早绑定—>晚绑定,继承—>组合,编译时依赖—>运行时依赖,紧耦合—>松耦合

  5. 现在被其他更科学地方法替代的设计模式:Builder,Mediator,Memento,Iterator,Chain of Resposibility,Command,Visitor,Interpreter

  6. 最常用的结构:(组合一个指针而不是通过继承的方式)

    class A{
    	B* pb;
    	//
    }
    
  7. 什么时候不用模式:代码可读性很差时,需求理解还很浅时,变化还没有显现时,不是系统的关键依赖点,项目没有复用价值时,项目将要发布时。

  8. 经验之谈:不要为模式而模式,关注抽象类&接口,理清变化点和稳定点,审视依赖关系,药有Framwork和Application的区隔思维,良好的设计是演化的结果

  9. 设计模式成长之路:

  10. 不识不知

  11. 能识能用

  12. 能框架设计

  13. 忘掉模式,只有原则。能够良好解决问题即可。

更多推荐

设计模式(二)23种设计模式