书籍
《设计模式-可复⽤⾯向对象软件的基础》
《重构与模式》
设计模式
设计模式是指在软件开发中,经过验证的,⽤于解决在特定环境下,重复出现的,特定问题的解决⽅案;
内存模型:
扩展:c语⾔当中的多态
redis
nginx
模式设计原则:
依赖倒置原则
⾼层模块不应该依赖低层模块,⼆者都应该依赖抽象;
抽象不应该依赖具体实现,具体实现应该依赖于抽象;
⾃动驾驶系统公司是⾼层,汽⻋⽣产⼚商为低层,它们不应该互相依赖,⼀⽅变动另⼀⽅也会
跟着变动;⽽应该抽象⼀个⾃动驾驶⾏业标准,⾼层和低层都依赖它;这样以来就解耦了两⽅
的变动;⾃动驾驶系统、汽⻋⽣产⼚商都是具体实现,它们应该都依赖⾃动驾驶⾏业标准(抽
象);
开放封闭原则
⼀个类应该对扩展开放,对修改关闭;
⾯向接⼝编程
不将变量类型声明为某个特定的具体类,⽽是声明为某个接⼝。客户程序⽆需获知对象的具体类型,只需要知道对象所具有的接⼝。
减少系统中各部分的依赖关系,从⽽实现“⾼内聚、松耦合”的类型设计⽅案。
封装变化点
将稳定点和变化点分离,扩展修改变化点;让稳定点与变化点的实现层次分离;
单⼀职责原则
⼀个类应该仅有⼀个引起它变化的原因;
⾥⽒替换原则
⼦类型必须能够替换掉它的⽗类型;主要出现在⼦类覆盖⽗类实现,原来使⽤⽗类型的程序可
能出现错误;覆盖了⽗类⽅法却没实现⽗类⽅法的职责;
接⼝隔离原则
不应该强迫客户依赖于他们不⽤的⽅法;
⼀般⽤于处理⼀个类拥有⽐较多的接⼝,⽽这些接⼝涉及到很多职责;
对象组合优于类继承
继承耦合度⾼,组合耦合度低;
什么情况下使⽤设计模式?
系统的关键依赖点;
能明确找到变化点;
能明确找到复⽤⽅向;
对需求变化⽅向熟悉;
如何找到设计模式?
从重构中获得;
重构
静态转变为动态;
早绑定转变为晚绑定;
继承转变为组合;
编译时依赖转变为运⾏时依赖;
紧耦合转变为松耦合;
为什么要学习设计模式?
从已有的且证明有效的设计模式中获取灵感,少⾛弯路;
通⽤语⾔,知道在已有的设计模式扩展代码;
体会模式设计,设计⾃⼰的⾏之有效的设计模式;
学习设计模式的步骤
深刻体会上⾯的原则;
理解设计模式,能知道设计模式的变化点和稳定点;
能在已使⽤的设计模式中,知道如何写扩展;
能在复杂需求中,抽象出已有设计模式;能在重构中,开发⾃⼰的设计模式;
能在重构中,开发⾃⼰的设计模式;
模板⽅法
定义
定义⼀个操作中的算法的⻣架 ,⽽将⼀些步骤延迟到⼦类中。Template Method使得⼦类可以不
改变⼀个算法的结构即可重定义该算法的某些特定步骤。——《 设计模式》GoF
背景
某个品牌动物园,有⼀套固定的表演流程,但是其中有若⼲个表演⼦流程受欢迎程度⽐较低,希望
将这⼏个表演流程创新,以尝试迭代更新表演流程;
代码
#include <iostream> using namespace std; class ZooShow { public: // 固定流程封装到这里 void Show() { Show0(); Show1(); Show2(); Show3(); } protected: // 子流程 使用protected保护起来 不被客户调用 但允许子类扩展 virtual void Show0(){ cout << "show0" << endl; } virtual void Show2(){ cout << "show2" << endl; } virtual void Show1() { } virtual void Show3() { } }; class ZooShowEx : public ZooShow { protected: virtual void Show1(){ cout << "show1" << endl; } virtual void Show3(){ cout << "show3" << endl; } virtual void Show4() { // } }; class ZooShowEx1 : public ZooShow { protected: virtual void Show0(){ cout << "show1" << endl; } virtual void Show2(){ cout << "show3" << endl; } }; class ZooShowEx2 : public ZooShow { protected: virtual void Show1(){ cout << "show1" << endl; } virtual void Show2(){ cout << "show3" << endl; } }; /* 依赖倒置原则 单一职责原则 接口隔离原则 反向控制:应用程序 框架 应用程序(变化的)应该依赖框架(稳定的),应该是框架去调应用程序,而不是应用程序去调框架 */ int main () { ZooShow *zs = new ZooShowEx; ZooShow *zs1 = new ZooShowEx1; ZooShow *zs2 = new ZooShowEx2; zs->Show(); return 0; }
要点
⾮常常⽤的设计模式,⼦类可以复写⽗类的⼦流程,使⽗类的⼤流程更丰富;
反向控制流程的典型应⽤;
⽗类protected 保护⼦类需要复写的⼦流程;这样⼦类的⼦流程只能⽗类来调⽤;
充分体现了依赖倒置原则;
本质
通过固定算法⻣架来约束⼦类的⾏为;
结构图
观察者模式
定义
定义对象间的⼀种⼀对多(变化)的依赖关系,以便当⼀个对象(Subject)的状态发⽣改变时,所有
依赖于它的对象都得到通知并⾃动更新。——《 设计模式》GoF
背景
⽓象站发布⽓象资料给数据中⼼,数据中⼼经过处理,将⽓象信息更新到两个不同的显示终端(A
和B);
代码
#include <vector> class IDisplay { public: virtual void Show(float temperature) = 0; virtual ~IDisplay() {} }; class DisplayA : public IDisplay { public: virtual void Show(float temperature); }; class DisplayB : public IDisplay{ public: virtual void Show(float temperature); }; class WeatherData { }; class DataCenter { public: void Attach(IDisplay * ob); void Detach(IDisplay * ob); void Notify() { float temper = CalcTemperature(); for (auto iter = obs.begin(); iter != obs.end(); iter++) { (*iter)->Show(temper); } } private: virtual WeatherData * GetWeatherData(); virtual float CalcTemperature() { WeatherData * data = GetWeatherData(); // ... float temper/* = */; return temper; } std::vector<IDisplay*> obs; }; int main() { DataCenter *center = new DataCenter; IDisplay *da = new DisplayA(); IDisplay *db = new DisplayB(); center->Attach(da); center->Attach(db); center->Notify(); //----- center->Detach(db); center->Notify(); return 0; }
要点
观察者模式使得我们可以独⽴地改变⽬标与观察者,从⽽使⼆者之间的关系松耦合;
观察者⾃⼰决定是否订阅通知,⽬标对象并不关注谁订阅了;
观察者不要依赖通知顺序,⽬标对象也不知道通知顺序;
常使⽤在基于事件的ui框架中,也是MVC的组成部分;
常使⽤在分布式系统中,actor框架中;
本质
触发联动;
结构图
策略模式
定义
定义⼀系列算法,把它们⼀个个封装起来,并且使它们可互相替换。该模式使得算法可独⽴于使⽤
它的客户程序⽽变化。——《设计模式》GoF
背景
某商场节假⽇有固定促销活动,为了加⼤促销⼒度,现提升国庆节促销活动规格;
代码
要点
策略模式提供了⼀系列可重⽤的算法,从⽽可以使得类型在运⾏时⽅便地根据需要在各个算法
之间进⾏切换;
策略模式消除了条件判断语句;就是在解耦合;
充分体现了开闭原则;单⼀职责;
本质
分离算法,选择实现;
结构图
责任链模式
定义
使多个对象都有机会处理请求,从⽽避免请求的发送者和接收者之间的耦合关系。将这些对象连成
⼀条链,并沿着这条链传递请求,直到有⼀个对象处理它为⽌。——《设计模式》GoF
背景
请假流程,1天内需要主程序批准,3天内需要项⽬经理批准,3天以上需要⽼板批准;
代码
class Context { public: std::string name; int day; }; class IHandler { public: virtual ~IHandler() {} void SetNextHandler(IHandler *next) { next = next; } bool Handle(ctx) { if (CanHandle(ctx)) { return HandleRequest(); } else if (GetNextHandler()) { return GetNextHandler()->HandleRequest(ctx); } else { // err } } protected: virtual bool HandleRequest(const Context &ctx) = 0; virtual bool CanHandle(const Context &ctx) =0; IHandler * GetNextHandler() { return next; } private: IHandler *next; }; class HandleByMainProgram : public IHandler { protected: virtual bool HandleRequest(const Context &ctx){ // } virtual bool CanHandle() { // } }; class HandleByProjMgr : public IHandler { protected: virtual bool HandleRequest(const Context &ctx){ // } virtual bool CanHandle() { // } }; class HandleByBoss : public IHandler { public: virtual bool HandleRequest(const Context &ctx){ // } protected: virtual bool CanHandle() { // } }; int main () { IHandler * h1 = new MainProgram(); IHandler * h2 = new HandleByProjMgr(); IHandler * h3 = new HandleByBoss(); h1->SetNextHandler(h2); Context ctx; h1->handle(ctx); return 0; }
nginx 阶段处理
// 严格意义说是功能链
// ngx_http_init_phase_handlers 初始化责任链cmcf->phase_engine.handlers
// ngx_http_core_run_phases 调⽤责任链
要点
解耦请求⽅和处理⽅,请求⽅不知道请求是如何被处理,处理⽅的组成是由相互独⽴的⼦处理
构成,⼦处理流程通过链表的⽅式连接,⼦处理请求可以按任意顺序组合;
责任链请求强调请求最终由⼀个⼦处理流程处理;通过了各个⼦处理条件判断;
责任链扩展就是功能链,功能链强调的是,⼀个请求依次经由功能链中的⼦处理流程处理;
充分体现了单⼀职责原则;将职责以及职责顺序运⾏进⾏抽象,那么职责变化可以任意扩展,
同时职责顺序也可以任意扩展;
本质
分离职责,动态组合;
结构图
装饰器模式
定义
动态地给⼀个对象增加⼀些额外的职责。就增加功能⽽⾔,装饰器模式⽐⽣成⼦类更为灵活。——
《设计模式》GoF
背景
普通员⼯有销售奖⾦,累计奖⾦,部⻔经理除此之外还有团队奖⾦;后⾯可能会添加环⽐增⻓奖
⾦,同时可能针对不同的职位产⽣不同的奖⾦组合;
代码
// 普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能产生不同的奖金组合; // 销售奖金 = 当月销售额 * 4% // 累计奖金 = 总的回款额 * 0.2% // 部门奖金 = 团队销售额 * 1% // 环比奖金 = (当月销售额-上月销售额) * 1% // 销售后面的参数可能会调整 class Context { public: bool isMgr; // User user; // double groupsale; }; // 试着从职责出发,将职责抽象出来 class CalcBonus { public: CalcBonus(CalcBonus * c = nullptr) {} virtual double Calc(Context &ctx) { return 0.0; // 基本工资 } virtual ~CalcBonus() {} protected: CalcBonus* cc; }; class CalcMonthBonus : public CalcBonus { public: CalcMonthBonus(CalcBonus * c) : cc(c) {} virtual double Calc(Context &ctx) { double mbonus /*= 计算流程忽略*/; return mbonus + cc->Calc(ctx); } }; class CalcSumBonus : public CalcBonus { public: CalcSumBonus(CalcBonus * c) : cc(c) {} virtual double Calc(Context &ctx) { double sbonus /*= 计算流程忽略*/; return sbonus + cc->Calc(ctx); } }; class CalcGroupBonus : public CalcBonus { public: CalcGroupBonus(CalcBonus * c) : cc(c) {} virtual double Calc(Context &ctx) { double gbnonus /*= 计算流程忽略*/; return gbnonus + cc->Calc(ctx); } }; class CalcCycleBonus : public CalcBonus { public: CalcGroupBonus(CalcBonus * c) : cc(c) {} virtual double Calc(Context &ctx) { double gbnonus /*= 计算流程忽略*/; return gbnonus + cc->Calc(ctx); } }; int main() { // 1. 普通员工 Context ctx1; CalcBonus *base = new CalcBonus(); CalcBonus *cb1 = new CalcMonthBonus(base); CalcBonus *cb2 = new CalcSumBonus(cb1); cb2->Calc(ctx1); // 2. 部门经理 Context ctx2; CalcBonus *cb3 = new CalcGroupBonus(cb2); cb3->Calc(ctx2); }
要点
通过采⽤组合⽽⾮继承的⼿法, 装饰器模式实现了在运⾏时动态扩展对象功能的能⼒,⽽且
可以根据需要扩展多个功能。 避免了使⽤继承带来的“灵活性差”和“多⼦类衍⽣问题”。不是解决“多⼦类衍⽣的多继承”问题,⽽是解决“⽗类在多个⽅向上的扩展功能”问题;装饰器模式把⼀系列复杂的功能分散到每个装饰器当中,⼀般⼀个装饰器只实现⼀个功能,实现复⽤装饰器的功能;
本质
动态组合
结构图
单例模式
定义
保证⼀个类仅有⼀个实例,并提供⼀个该实例的全局访问点。——《设计模式》GoF
代码
版本1
线程不安全,且内存泄漏
// 内存栈区 // 内存堆区 // 常数区 // 静态区 系统释放 // ⼆进制代码区 class Singleton{ public: static Singleton* GetInstance(){ if(_instance == nullptr){ _instance = new Singleton(); } return _instance; } private: Singleton(){} // 构造 Singleton(const Singleton& that){} // 拷贝构造 Singleton& operator=(const Singleton& that){} // 拷贝赋值 static Singleton* _instance; }; Singleton* Singleton::_instance == nullptr;// 静态成员需要初始化
版本2
class Singleton { public: static Singleton * GetInstance() { if (_instance == nullptr) { _instance = new Singleton(); atexit(Destructor); } return _instance; } ~Singleton() {} /* class GC{ ~GC(){ delete _instance; } } */ private: static void Destructor() { if (nullptr != _instance) { delete _instance; _instance = nullptr; } } Singleton();//构造 Singleton(const Singleton &cpy); //拷⻉构造 Singleton& operator=(const Singleton&) {} static Singleton * _instance; /* static GC gc; */ } Singleton* Singleton::_instance = nullptr;//静态成员需要初始化 /* Singleton::GC gc; */ // 还可以使⽤ 内部类,智能指针来解决; 此时还有线程安全问题
版本3
#include <mutex> class Singleton { // 懒汉模式 lazy load public: static Singleton * GetInstance() { //std::lock_guard<std::mutex> lock(_mutex); // 3.1 切换线程 锁的粒度大 if (_instance == nullptr) { std::lock_guard<std::mutex> lock(_mutex); // 3.2 if (_instance == nullptr) { /*pthread_once_t once_control = PTHREAD_ONCE_INIT; pthread_once(once_control , init);*/ _instance = new Singleton(); // ---> 1分配内存,2调用构造函数,3赋值 atexit(Destructor); // ---> 多线程环境下 cpu会进行重排 } // ---> 可能调用顺序是132 } // 晚到来的线程拿到_instance不等于null return _instance; // 直接返回,此时构造函数还没执行完 } private: static void init(void){ _instance = new Singleton(); } static void Destructor() { if (nullptr != _instance) { delete _instance; _instance = nullptr; } } Singleton(){} //构造 Singleton(const Singleton &cpy){} //拷⻉构造 Singleton& operator=(const Singleton&) {} static Singleton * _instance; static std::mutex _mutex; } Singleton* Singleton::_instance = nullptr;//静态成员需要初始化 std::mutex Singleton::_mutex; //互斥锁初始化
版本4
#include <atomic> //懒汉式 #include <mutex> class Singleton{ public: static Singleton* GetInstance(){ Singleton* tmp = _instance.load(std::memory_order_relaxed);//把_instance取出 std::atomic_thread_fence(std::memory_order_acquire);// 获取内存屏障 if(tmp == nullptr){ std::lock_guard<std::mutex> lock(_mutex); tmp = _instance.load(std::memory_order_relaxed); if(tmp == nullptr){ tmp = new Singleton; std::atomic_thread_fence(std::memory_order_release);//释放内存屏障 _instance.store(tmp , std::memory_order_relaxed);//把tmp存入_instance atexit(Destructor); } } return tmp; } private: static void Destructor(){ Singleton* tmp = _instance.load(std::memory_order_relaxed); if(nullptr != tmp){ delete tmp; } } Singleton(){} Singleton(const Singleton& that){} Singleton& operator=(const Singleton& that){} static std::atomic<Singleton*> _instance; static std::mutex _mutex; }; std::atomic<Singleton*> Singleton::_instance; std::mutex Singleton::_mutex;
版本5
// c++11 magic static 特性:如果当静态变量在初始化的时候,并发同时进⼊声明语句,并发 线程将会阻塞等待初始化结束。 class Singleton { public: ~Singleton(){} static Singleton& GetInstance() { static Singleton instance; return instance; } private: //构造函数私有,无法继承,子类无法实例化父类 Singleton(){} Singleton(const Singleton&) {} Singleton& operator=(const Singleton&) {} }; // 继承 Singleton // g++ Singleton.cpp -o singleton -std=c++11 /*该版本具备 版本5 所有优点: 1. 利⽤静态局部变量特性,延迟加载; 2. 利⽤静态局部变量特性,系统⾃动回收内存,⾃动调⽤析构函数; 3. 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作; 4. c++11 静态局部变量初始化时,具备线程安全; */
版本6
template<typename T> class Singleton { public: static T& GetInstance() { static T instance; // 这⾥要初始化DesignPattern,需要调⽤ DesignPattern 构造函数,同时会调⽤⽗类的构造函数。 return instance; } protected: virtual ~Singleton() {} Singleton() {} // protected修饰构造函数,才能让别⼈继承 Singleton(const Singleton&) {} Singleton& operator =(const Singleton&) {} }; class DesignPattern : public Singleton<DesignPattern> { friend class Singleton<DesignPattern>; // friend 能让 Singleton<T> 访 问到 DesignPattern构造函数 private: DesignPattern(){} DesignPattern(const DesignPattern&) {} DesignPattern& operator=(const DesignPattern&) {} }
要点
结构图
策略模式
定义
定义⼀系列算法,把它们⼀个个封装起来,并且使它们可互相替换。该模式使得算法可独⽴于使⽤
它的客户程序⽽变化。——《设计模式》GoF
背景
某商场节假⽇有固定促销活动,为了加⼤促销⼒度,现提升国庆节促销活动规格;
代码
要点
策略模式提供了⼀系列可重⽤的算法,从⽽可以使得类型在运⾏时⽅便地根据需要在各个算法
之间进⾏切换;
策略模式消除了条件判断语句;就是在解耦合;
充分体现了开闭原则;单⼀职责;
本质
分离算法,选择实现;
结构图
责任链模式
定义
使多个对象都有机会处理请求,从⽽避免请求的发送者和接收者之间的耦合关系。将这些对象连成
⼀条链,并沿着这条链传递请求,直到有⼀个对象处理它为⽌。——《设计模式》GoF
背景
请假流程,1天内需要主程序批准,3天内需要项⽬经理批准,3天以上需要⽼板批准;
代码
nginx 阶段处理
// 严格意义说是功能链
// ngx_http_init_phase_handlers 初始化责任链cmcf->phase_engine.handlers
// ngx_http_core_run_phases 调⽤责任链
要点
解耦请求⽅和处理⽅,请求⽅不知道请求是如何被处理,处理⽅的组成是由相互独⽴的⼦处理
构成,⼦处理流程通过链表的⽅式连接,⼦处理请求可以按任意顺序组合;
责任链请求强调请求最终由⼀个⼦处理流程处理;通过了各个⼦处理条件判断;
责任链扩展就是功能链,功能链强调的是,⼀个请求依次经由功能链中的⼦处理流程处理;
充分体现了单⼀职责原则;将职责以及职责顺序运⾏进⾏抽象,那么职责变化可以任意扩展,
同时职责顺序也可以任意扩展;
本质
分离职责,动态组合;
结构图
⼯⼚⽅法模式
定义
定义⼀个⽤于创建对象的接⼝,让⼦类决定实例化哪⼀个类。Factory Method使得⼀个类的实例化
延迟到⼦类。——《设计模式》GoF
背景
实现⼀个导出数据的接⼝,让客户选择数据的导出⽅式;
代码
要点
解决创建过程⽐较复杂,希望对外隐藏这些细节;
⽐如连接池,线程池;
隐藏对象真实类型;
对象创建会有很多参数来决定如何创建;
创建对象有复杂的依赖关系;
本质
延迟到⼦类来选择实现;
结构图
抽象⼯⼚模式
定义
提供⼀个接⼝,让该接⼝负责创建⼀系列“相关或者相互依赖的对象”,⽆需指定它们具体的类。
——《设计模式》GoF
背景
实现⼀个拥有导出导⼊数据的接⼝,让客户选择数据的导出导⼊⽅式;
代码
要点
本质
结构图
适配器模式
定义
将⼀个类的接⼝转换成客户希望的另⼀个接⼝。Adapter模式使得原本由于接⼝不兼容⽽不能⼀起
⼯作的那些类可以⼀起⼯作。——《设计模式》GoF
背景
⽇志系统,原来是通过写磁盘的⽅式进⾏存储,后来因为查询不便,需要额外添加往数据库写⽇志
的功能(写⽂件和数据库并存);
代码
要点
原来的接⼝是稳定的,新的外来的需求是变化的,那么可以通过继承原来的接⼝,让原来的接
⼝继续保持稳定,在⼦类通过组合的⽅式来扩展功能;
本质
转换匹配,复⽤功能;
结构图
代理模式
定义
为其他对象提供⼀种代理以控制对这对象的访问。——《设计模式》GoF
背景
在有些系统中,为了某些对象的纯粹性,只进⾏了功能相关封装(稳定点),后期添加了其他功能
需要对该对象进⾏额外操作(变化点),为了隔离变化点(也就是不直接在稳定点进⾏修改,这样
会让稳定点也变得不稳定),可以抽象⼀层代理层;
代码
要点
远程代理(隐藏⼀个对象存在不同的地址空间的事实),虚代理(延迟加载lazyload),保护
代理(在代理前后做额外操作,权限管理,引⽤计数等);
在分布式系统中,actor模型(skynet)等常⽤的设计模式;
本质
控制对象访问;
结构图