文章目录
- 1. 基础
- ①如何学习设计模式
- ② 类模型
- ③ 类关系
- 2. 设计原则
- 3. 模板方法
- ① 定义
- ②背景
- ③ 要点
- ④ 本质
- ⑤ 结构图
- ⑥ 样例代码
- 4. 观察者模式
- ① 定义
- ②背景
- ③ 要点
- ④ 本质
- ⑤ 结构图
- ⑥ 样例代码
- 5. 策略模式
- ① 定义
- ②背景
- ③ 要点
- ④ 本质
- ⑤ 结构图
- ⑥ 样例代码
1. 基础
①如何学习设计模式
- 找稳定点和变化点,把变化点隔离出来(解耦合)
- 先满足设计原则,慢慢迭代出设计模式(抽象思维、分治思维)
② 类模型
③ 类关系
- Stranger可以访问TonyFather中的public成员变量
- TonyFather可以访问TonyFather中的public、protected成员变量
- Beauty可以访问TonyFather中的public、protected以及private成员变量,但是TonyFather是无法访问Beauty中的protected和private成员变量的,因为友元是单向的
- TonyMother与访问TonyFather可以互相访问对方的所有成员变量,因为两者都声明对方为友元
2. 设计原则
- 开放封闭原则(Open Closed Principle,OCP):对扩展(组合和继承)开放,对修改关闭
- 单一职责原则(Single Responsibility Principle,SRP):一个类或模块只负责一个功能,即一个类应该仅有一个引起它变化的原因
- 里氏替换原则(Liskov Substitution Principle,LSP):子类可以替换父类,并且保持程序的正确性
- 接口隔离原则(Interface Segregation Principle,ISP):接口应该尽量小而专一,不应该强迫客户依赖于它们不用的方法,避免臃肿和多余;一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责
- 面向接口原则(Interface Oriented Principle,IOP):不将变量类型声明为某个特定的具体类,而是声明为某个接口;客户程序无需获知对象的具体类型,只需要知道对象所具有的接口;减少系统中各个部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案
- 依赖倒置原则(Dependence Inversion Principle,DIP):高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖具体实现,具体实现应该依赖于抽象
- 合成复用原则(Composite Reuse Principle,CRP):即组合优于继承原则,优先使用组合或聚合而不是继承来实现复用;继承耦合度高,组合耦合度低
- 封装变化点原则(Package Change Point Principle,PCPP):将稳定点和变化点分离,扩展修改变化点;让稳定点和变化点实现层次分离
- 迪米特法则(Law of Demeter,LoD):一个对象应该尽量少地了解其他对象
前四个设计原则最为重要
3. 模板方法
① 定义
定义一个操作中的算法骨架,而将一些步骤延迟到子类中
Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
②背景
某一个表演有一套特定的展示流程,但是其中有若干个表演子流程可创新替换,宜昌市迭代更新表演流程
③ 要点
- 最常用的设计模式,子类可以复写父类子流程,使弗雷德骨架流程丰富
- 反向控制流程的典型应用
- 父类protected保护子类需要复写的子流程,这样子类的子流程只能父类来调用
④ 本质
通过固定算法骨架来约束子类的行为
⑤ 结构图
⑥ 样例代码
#include <iostream>
using namespace std;
class Perform {
public:
void show() {
if (show0())
playGame();
show1();
show2();
show3();
}
private:
void playGame() {
cout << "show0 end, have a small game" << endl;
}
protected:
virtual bool show0() {
cout << "show0" << endl;
return true;
}
virtual void show1() {
}
virtual void show2() {
cout << "show2" << endl;
}
virtual void show3() {
}
};
class PerformEx1 : public Perform {
protected:
virtual bool show0() {
cout << "ex1 show0" << endl;
return true;
}
virtual void show2() {
cout << "ex1 show2" << endl;
}
};
class PerformEx2 : public Perform {
protected:
virtual void show1() {
cout << "ex2 show1" << endl;
}
virtual void show2() {
cout << "ex2 show2" << endl;
}
};
class PerformEx3 : public Perform {
protected:
virtual void show1() {
cout << "ex3 show1" << endl;
}
virtual void show2() {
cout << "ex3 show2" << endl;
}
virtual void show3() {
cout << "ex3 show3" << endl;
}
};
int main()
{
Perform *p1 = new PerformEx1();
p1->show();
cout << "====================" << endl;
Perform *p2 = new PerformEx2();
p2->show();
cout << "====================" << endl;
Perform *p3 = new PerformEx3();
p3->show();
cout << "====================" << endl;
return 0;
}
补充:
- protected修饰符修饰类的成员可以被同一个类中的其他成员函数访问,也可以被派生类中的成员函数访问,但不能被其他地方访问;protected修饰符适用于继承关系中,保护基类的成员不被外部修改,但允许派生类修改
- virtual关键字的作用是用来定义虚函数,实现动态绑定。虚函数是指在基类中声明为virtual的成员函数,它可以在派生类中被重写;当通过基类类型的指针或引用调用虚函数时,会根据对象的实际类型调用相应的重写版本;virtual关键字也可以用于虚继承,避免多重继承时出现菱形继承问题
- virtual function() = 0的作用是定义纯虚函数。纯虚函数是一种没有实现的虚函数,它只有声明,没有定义;纯虚函数的目的是让派生类去重写它,实现多态;如果一个类中有纯虚函数,那么这个类就是抽象类,不能被实例化
4. 观察者模式
① 定义
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新
②背景
气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到不同的显示终端上
③ 要点
- 观察者模式使得我们可以独立地改变目标与观察者,从而使二者之间的关系松耦合
- 观察者自己决定是否订阅通知,目标对象并不关注谁订阅了
- 观察者不要依赖通知顺序,目标对象也不知道通知顺序
- 常用在基于事件的ui框架中,也是MVC的组成部分
- 常用在分布式系统和actor框架中
- zk、etcd、kafka、redis、分布式锁
④ 本质
触发联动
⑤ 结构图
⑥ 样例代码
#include <iostream>
#include <vector>
using namespace std;
class IDisplay
{
public:
virtual void show(float temperature) = 0;
virtual ~IDisplay() {}
};
class DisplayA : public IDisplay
{
public:
virtual void show(float temperature);
private:
void ownFunction(); // 可以在子类上继续添加自己需要的函数
};
class DisplayB : public IDisplay
{
public:
virtual void show(float temperature);
};
class DisplayC : public IDisplay
{
public:
virtual void show(float temperature);
};
class WeatherData
{
};
class DataCenter
{
public:
bool attach(IDisplay *ob);
bool detach(IDisplay *ob);
void notify()
{
float temperature = CalcTemperature();
for (auto iter = this->obs.begin(); iter != obs.end(); iter++)
(*iter)->show(temperature);
}
private:
virtual WeatherData *GetWeatherData();
virtual float CalcTemperature()
{
WeatherData *data = GetWeatherData();
// ...
float temperature;
return temperature;
}
std::vector<IDisplay *> obs;
};
int main()
{
DataCenter *center = new DataCenter;
IDisplay *da = new DisplayA();
IDisplay *db = new DisplayB();
IDisplay *dc = new DisplayC();
center->attach(da);
center->attach(db);
center->attach(dc);
center->notify();
center->detach(db);
center->notify();
return 0;
}
5. 策略模式
① 定义
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换,该模式使得算法可独立于使用它的客户程序而变化
②背景
某商场节假日有固定促销活动,为了加大促销力度,现提升国庆节促销活动规格
③ 要点
- 策略模式提供了一系列可重用的算法,从而可以使得类型在运⾏时方便地根据需要在各个算法之间进行切换
- 策略模式消除了条件判断语句,也就是在解耦合
④ 本质
分离算法,选择实现
⑤ 结构图
⑥ 样例代码
#include <iostream>
using namespace std;
class Context
{
// 环境类
};
class ProStrategy
{
// 抽象的策略类
public:
// 算法接口
virtual double CalcPro(const Context &ctx) = 0;
virtual ~ProStrategy();
};
// 派生出的具体策略类
class VAC_Spring : public ProStrategy {
public:
virtual double CalcPro(const Context &ctx){}
};
class VAC_Labor : public ProStrategy {
public:
virtual double CalcPro(const Context &ctx){}
};
class VAC_Labor1 : public VAC_Labor {
public:
virtual double CalcPro(const Context &ctx){}
};
class VAC_National : public ProStrategy {
public:
virtual double CalcPro(const Context &ctx){}
};
class VAC_Christmas : public ProStrategy {
public:
virtual double CalcPro(const Context &ctx){}
};
class Promotion
{
public:
Promotion(ProStrategy *s) : s(s){}
~Promotion(){}
double CalcPromotion(const Context &ctx) {
return s->CalcPro(ctx);
}
private:
ProStrategy *s;
};
int main()
{
Context ctx;
ProStrategy *s = new VAC_Labor1();
Promotion *p = new Promotion(s);
p->CalcPromotion(ctx);
return 0;
}
补充:
- 析构函数设为虚函数:是为了实现多态性,即当用父类指针删除子类对象时,能够正确地调用子类的析构函数,从而避免内存泄漏或其他错误;如果父类的析构函数不是虚函数,那么只会调用父类的析构函数,导致子类的资源没有被释放