设计模式分类
设计模式可以分为三种类型:创建型设计模式、结构型设计模式和行为型设计模式。
创建型设计模式:这些模式涉及到对象的创建机制,包括简单工厂模式、工厂方法模式、抽象工厂模式、单例模式、建造者模式和原型模式。
结构型设计模式:这些模式涉及到类和对象的组合,包括适配器模式、桥接模式、组合模式、装饰器模式、外观模式、享元模式和代理模式。
行为型设计模式:这些模式涉及到对象之间的通信和交互,包括责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。
本文是对行为型设计模式中的模板、观察者、策略设计模式的一个总结。每个设计模式的定义都比较晦涩,可以直接看代码理解。
设计模式的设计原则
依赖倒置:高层模块不应该依赖低层模块,两者都应该依赖抽象; 抽象不应该依赖具体实现,具体实现应该依赖于抽象; (记住依赖抽象就好了)。
开放封闭:一个类应该对扩展(组合和继承)开放,对修改关闭;
面向接口:不将变量类型声明为某个特定的具体类,而是声明为某个接口;
客户程序无需获知对象的具体类型,只需要知道对象所具有的接口;
减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案;(记住只暴露接口,只调用接口)。
封装变化点:将稳定点和变化点分离,扩展修改变化点;让稳定点和变化点的实现层次分离;
单一职责:一个类应该仅有一个引起它变化的原因; (就是变化点不要太多)。
里氏替换:子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可能出现错误;覆盖了父类方法却没有实现父类方法的职责;( 就是子类可以覆盖父类的方法,但是得保证父类必要的功能)。
接口隔离:不应该强迫客户依赖于它们不用的方法;
一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责;
客户端不应该依赖它不需要的接口。
一个类对另一个类的依赖应该建立在最小的接口上。
组合优于继承:继承耦合度高,组合耦合度低;
模板设计模式
定义一个操作中的算法的骨架 ,而将一些步骤延迟到子类中。 Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
通过一个动物园表演的例子来说明该设计模式:
某个品牌动物园,有一套固定的表演流程,但是其中有若干个表演子流程可创新替换,以尝试迭代更新表演流程;
#include <iostream> using namespace std; // 开闭原则 class ZooShow { public: void Show() { //一套固定的表演流程,show0->show1->show2->show3 // 加了一个特定的设定:show1表演流程没有超时的话,进行一个中场游戏环节;如果超时,直接入下一个子表演流程 if (Show0()) PlayGame(); Show1(); Show2(); Show3(); } private: void PlayGame() { cout << "after Show0, then play game" << endl; } // 通过protected对其他用户关闭,但是子类开放的 protected: //判断是否超时的变量 bool expired; virtual bool Show0() { cout << "show0" << endl; //如果没有超时,返回true,超时了返回false if (! expired) { return true; } return false; } virtual void Show1() { cout << "show1" << endl; } //抽象接口 virtual void Show2() = 0; virtual void Show3() { cout << "show3" << endl; } }; // 框架 // 模板方法模式 class NewShow1 : public ZooShow { protected: //替换了原来的表演1 virtual void Show1(){ cout << "Newshow1 show1" << endl; } //实现了抽象接口 virtual void Show2(){ cout << "Newshow1 show2" << endl; } }; class NewShow2 : public ZooShow { protected: //替换了表演0,但是仍要实现原来的特定功能(里氏替换) virtual bool Show0() { cout << "NewShow2 show0" << endl; if (! expired) { // 里氏替换 return true; } return false; } virtual void Show2(){ cout << "Newshow2 show2" << endl; } }; int main () { ZooShow *zs1 = new NewShow1; // 晚绑定 ZooShow *zs2 = new NewShow2; zs1->Show(); zs2->Show(); return 0; }
结构:
- 算法骨架类:用于实现方法的调度逻辑。以及为实际方法实现提供一个接口(通常是抽象类)。
- 具体方法类:实现算法骨架类中需要调度的具体的方法实现。
使用场景
多个方法有固定的使用逻辑。并且支持特点方法的扩展。支持共有方法复用和私有方法定制化的场景。
核心点:
- 算法骨架:模板模式适用于有这种算法骨架的场景。也就是说各个方法有个固定的执行逻辑。
- 复用:子类复用父类的算法骨架
- 扩展:用户不用修改源框架的基础上,可以实现自己定制化的功能方法。
总结下模板设计模式的特点:
子类可以复写父类子流程,使父类的骨架流程丰富;
父类中的抽象方法由子类实现,属于一种反向控制流程;
父类protected 保护子类需要复写的子流程,这样子类的子流程只能父类来调用;
通过固定算法骨架来约束子类的行为;
观察者模式
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
通过一个气象站的例子来说明:
气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到两个不同的显示终端(A 、B和C);
#include <iostream> #include <list> #include <algorithm> using namespace std; // 定义一个抽象的基类,定义了显示温度的接口 class IDisplay { public: virtual void Show(float temperature) = 0; virtual ~IDisplay() {} }; //不同的显示终端具体实现显示接口 class DisplayA : public IDisplay { public: virtual void Show(float temperature) { cout << "DisplayA Show temperature : " << temperature << endl; } private: void jianyi(); }; class DisplayB : public IDisplay{ public: virtual void Show(float temperature) { cout << "DisplayB Show temperature : " << temperature << endl; } }; class DisplayC : public IDisplay{ public: virtual void Show(float temperature) { cout << "DisplayC Show temperature : " << temperature << endl; } }; // 应对稳定点,抽象 // 应对变化点,扩展(继承和组合) //定义一个数据中心,负责注册目标显示终端、删除显示终端、通知终端显示功能 class DataCenter { public: //注册显示终端 void Attach(IDisplay * ob) { obs.push_back(ob); } //删除显示终端 void Detach(IDisplay * ob) { obs.remove(ob); } //通知显示功能 void Notify(int temper_num) { float temper = SetTemperature(temper_num); for (auto iter : obs) { iter->Show(temper); } } // 接口隔离 private: float SetTemperature(int temper) { return temper; } std::list<IDisplay*> obs; }; int main() { DataCenter *center = new DataCenter; // ... 某个模块 IDisplay *da = new DisplayA(); center->Attach(da); IDisplay *db = new DisplayB(); center->Attach(db); IDisplay *dc = new DisplayC(); center->Attach(dc); center->Notify(36); //----- center->Detach(db); center->Notify(37); //.... return 0; }
结构:
- 抽象观察者类:给具体的观察者业务方法实现提供一个接口。
- 具体观察者类:继承抽象观察者类,实现具体的业务逻辑。
- 主题类:是具有状态的对象,维护着一个观察者列表。提供了添加、删除和通知观察者的方法。
使用场景
当对象间存在一对多关系时,则可以使用观察者模式(Observer Pattern)。
总结观察者模式的特点:
观察者模式使得我们可以独立地改变目标与观察者,从而使二者之间的关系松耦合;
观察者自己决定是否订阅通知,目标对象并不关注谁订阅了;
观察者不要依赖通知顺序,目标对象也不知道通知顺序;
策略设计模式
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。
用一个节假日销售价格的例子说明:
某商场节假日有固定促销活动,为了加大促销力度,现提升不同节假日促销活动规格;
#include <iostream> using namespace std; // 稳定点:使用抽象去解决它 // 变化点:可以通过扩展(继承和组合)去解决它 //策略基类,定义一个接口 class ProStategy { public: virtual void CalcPro(int ctx) = 0; }; // 策略子类,使用不同的方法实现 class VAC_QiXi : public ProStategy { public: virtual void CalcPro(int ctx){ cout << "QiXi : " << ctx << endl; } }; // cpp class VAC_Wuyi : public ProStategy { public: virtual void CalcPro(int ctx){ cout << "Wuyi : " << ctx << endl; } }; // cpp class VAC_GuoQing : public ProStategy { public: virtual void CalcPro(int ctx){ cout << "GuoQing : " << ctx << endl; } }; //调度类 class Context { public: Context(ProStategy *sss) : s(sss){} ~Context(){} void CalcPromotion(int ctx){ s->CalcPro(ctx); } private: ProStategy *s; }; int main () { ProStategy *s = new VAC_QiXi(); Context *p = new Context(s); p->CalcPromotion(1); s = new VAC_Wuyi(); p = new Context(s); p->CalcPromotion(2); s = new VAC_GuoQing(); p = new Context(s); p->CalcPromotion(3); return 0; }
结构:
- 抽象策略类:一个抽象类,给具体策略方法的实现提供一个接口。
- 具体策略类:实现具体的策略方法。
- 调度类:实现统一的调度逻辑,并给到用户使用。
使用场景
一个系统需要动态地使用几种策略方法中的一种时,就可以使用此设计模式。
策略设计模式的特点:
策略模式提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换;
策略模式消除了条件判断语句;也就是在解耦合;
分离算法,选择实现;
总结就是将对象和接口方法解耦,不同的算法可以以相同的形式调用,还可以节省实例化对象的开销。
模板设计模式VS策略设计模式
模板设计模式和策略设计模式还是有相似的点。都支持在定制私有的方法,即可以使用几种方法中的一种。
但是模板设计模式更强调一个统一的调度逻辑。策略方法更强调方法的使用策略。两者都基本能实现相同的功能,只是出发点有所不同。
模板设计模式适用于在一个算法的不同实现中保持算法的基本结构不变的情况。例如,在一个类中定义一个算法的基本结构,但是在子类中可以重写其中的某些步骤以实现不同的功能。
策略设计模式则适用于在运行时动态地改变一个算法的行为。例如,在一个类中定义一个算法的接口,但是在运行时可以传入不同的具体实现来改变算法的行为。
而且模板设计模式在编译时已确定算法的基本结构,而策略设计模式是在运行时动态地改变算法的行为(通过运行时传入不同的对象,实现不同功能的执行策略)。
责任链设计模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
通过一个请假的例子来解释该设计模式:
请假流程:请求流程,1 天内需要主管批准,1~3 天内需要项目经理批准,3 天以上需要总经理批准;
// ConsoleApplication2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <string> using namespace std; class Leader { public: string name; Leader* upLeader; // 下一个处理者 Leader(string name) : name(name), upLeader(nullptr) { } void setUpLeader(Leader* upLeader) { this->upLeader = upLeader; } // 审批操作 virtual void approve(int days, string employeeName) = 0; }; /** * 主管 */ class Director : public Leader { public: Director(string name) : Leader(name) {} void approve(int days, string employeeName)override { if (0 < days) { cout << "主管" << name << "审批:员工" << employeeName << "请假" << days << "天" << endl; } if (upLeader != nullptr) { upLeader->approve(days, employeeName); } } }; /** * 部门经理 */ class Manager : public Leader { public: Manager(string name) : Leader(name) {} void approve(int days, string employeeName) override { if (1 < days) { cout << "部门经理" << name << "审批:员工" << employeeName << "请假" << days << "天" << endl; } if (upLeader != nullptr) { upLeader->approve(days, employeeName); } } }; /** * 总经理 */ class GeneralManager : public Leader { public: GeneralManager(string name) : Leader(name) {} void approve(int days, string employeeName) override { if (3 < days) { cout << "总经理" << name << "审批:员工" << employeeName << "请假" << days << "天" << endl; } if (upLeader != nullptr) { upLeader->approve(days, employeeName); } } }; int main() { Director director("张三"); Manager manager("李四"); GeneralManager generalManager("小风"); director.setUpLeader(&manager); manager.setUpLeader(&generalManager); cout << "---------爱请假----------" << endl; director.approve(1, "爱"); cout << "---------自请假----------" << endl; director.approve(3, "自"); cout << "---------由请假----------" << endl; director.approve(100, "由"); }
结构
- 抽象处理者类:提供处理方法的接口,以及提供设置后一个处理者的方法;
- 具体处理者类:实现抽象接口中的处理方法(方法中还会调用后一个处理者的处理方法)。
使用场景
适用于一个请求需要多个步骤的处理流程。例如用户登录,需要进行用户是否存在、用户是否被锁定、密码校验等校验这一套流程。
责任链设计模式的特点:
- 解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合;
- 责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断;
- 责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理;
- 将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展,同时职责顺序也可以任意扩展;