如果你也跟笔者一样,初学模式的时候,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种设计模式都是最常用,也最好理解的。此外设计模式的八个原则,什么依赖倒置原则,里式替换原则也要熟记,面试很可能会让背。当然熟悉这几种设计模式也更能理解那些原则。以上。