如果你也跟笔者一样,初学模式的时候,20多种设计模式让你眼花缭乱,不知道如何下手,可以试试这里提出的方法。无论面试,或者重构代码的时候,都能知道相应设计模式的使用场景。简而言之,就是案例记忆法。通过记住每个设计模式的典型案例,达到对常用的几个设计模式了然于胸的境界。这里列举常用的5种设计模式,一般面试和简单的使用场景均能覆盖。如果希望掌握全部的设计模式,可以用这种方法总结剩下的模式。
模板方法模式 – 动物园表演流程
模板模式的关键词是,流程固定。这个设计模式适合流程固定的情况。比如动物园的表演流程有5个步骤(稳定点),其中某些步骤的表演内容需要改变(变化点)。
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 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; } }; // 反向调用,框架是固定的,仅可复写不同的show()子函数 int main () { ZooShow *zs1 = new ZooShowEx1; ZooShow *zs2 = new ZooShowEx2; zs1->Show(); return 0; }
观察者模式 – 数据中心显示
观察者模式,或者称之为发布/订阅模式,关键词是组装(attach)和拆卸(detach)。比如数据中心需要发送同一种数据(稳定点)到不同的显示终端显示,可以增加新的显示终端,可以删除旧的显示终端(变化点)。
// 不同的显示终端定义统一接口 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; }
策略模式 – 节日促销策略
策略模式的关键词是策略批量化。比如不同的节日做不同的促销活动。策略批量化就是每一种节日有一份自己的促销策略,用节日策略去构造促销对象。做促销本身是稳定的(即根据不同的节日有对应的促销活动),节日是变化的(可以有不同类型的节日),具体实现促销的方式也是变化的。
class ProStategy { public: virtual double CalcPro(const Context &ctx) = 0; virtual ~ProStategy(); }; //策略批量化: // 1.cpp class VAC_Spring : public ProStategy { public: virtual double CalcPro(const Context &ctx){} }; // 2.cpp class VAC_Wuyi : public ProStategy { public: virtual double CalcPro(const Context &ctx){} }; // 节日策略组合进促销策略类 class Promotion { public: Promotion(ProStategy *sss) : s(sss){} ~Promotion(){} double CalcPromotion(const Context &ctx){ return s->CalcPro(ctx); } private: ProStategy *s; }; // 用节日策略去构造促销对象 int main () { Context ctx; ProStategy *s = new VAC_QiXi1(); Promotion *p = new Promotion(s); p->CalcPromotion(ctx); return 0; }
责任链模式 – 请假流程
责任链模式用一句话替代,仅链条中的某一环有责任处理。比如请假流程,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; }
装饰器模式 – 公司奖金
装饰器模式的关键词是,一种功能作为一种装饰器,动态组合。对应的案例就是公司奖金,普通员工有销售奖金,累计奖金等。部门经理还有团队奖金。可能还会增加新人奖,老人奖,年度奖等等。
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); }
本来准备将工厂方法,抽象工厂,适配器,代理模式一起整理出来,但没有发现特别适合的案例。目前整理的这5种设计模式都是最常用,也最好理解的。此外设计模式的八个原则,什么依赖倒置原则,里式替换原则也要熟记,面试很可能会让背。当然熟悉这几种设计模式也更能理解那些原则。以上。