书籍
《设计模式
-
可复⽤⾯向对象软件的基础》
《重构与模式》
设计模式
设计模式是指在软件开发中,经过验证的,⽤于解决在特定环境下,重复出现的,特定问题的解决⽅案;
内存模型:
扩展:c语⾔当中的多态
redis
nginx
模式设计原则:
依赖倒置原则
⾼层模块不应该依赖低层模块,⼆者都应该依赖
抽象
;
抽象不应该依赖具体实现,具体实现应该依赖于抽象;
![](https://i-blog.csdnimg.cn/blog_migrate/6f35c17f485eb17232d3788b3681d9d9.png)
⾃动驾驶系统公司是⾼层,汽⻋⽣产⼚商为低层,它们不应该互相依赖,⼀⽅变动另⼀⽅也会
跟着变动;⽽应该
抽象
⼀个⾃动驾驶⾏业标准,⾼层和低层都依赖它;这样以来就解耦了两⽅
的变动;⾃动驾驶系统、汽⻋⽣产⼚商都是具体实现,它们应该都依赖⾃动驾驶⾏业标准(抽
象);
开放封闭原则
⼀个类应该对扩展开放,对修改关闭;
⾯向接⼝编程
不将变量类型声明为某个特定的具体类,⽽是声明为某个接⼝。
客户程序⽆需获知对象的具体类型,只需要知道对象所具有的接⼝。
减少系统中各部分的依赖关系,从⽽实现
“
⾼内聚、松耦合
”
的类型设计⽅案。
封装变化点
将稳定点和变化点分离,扩展修改变化点;
让稳定点与变化点的实现层次分离
;
单⼀职责原则
⼀个类应该仅有⼀个引起它变化的原因;
⾥⽒替换原则
⼦类型必须能够替换掉它的⽗类型;主要出现在⼦类覆盖⽗类实现,原来使⽤⽗类型的程序可
能出现错误;覆盖了⽗类⽅法却没实现⽗类⽅法的
职责
;
接⼝隔离原则
不应该强迫客户依赖于他们不⽤的⽅法;
⼀般⽤于处理⼀个类拥有⽐较多的接⼝,⽽这些接⼝涉及到很多职责;
对象组合优于类继承
继承
耦合度
⾼,
组合
耦合度低;
什么情况下使⽤设计模式?
系统的关键依赖点;
能明确找到变化点;
能明确找到复⽤⽅向;
对需求变化⽅向熟悉;
如何找到设计模式?
从重构中获得;
重构
静态转变为动态;
早绑定转变为晚绑定;
继承转变为组合;
编译时依赖转变为运⾏时依赖;
紧耦合转变为松耦合;
为什么要学习设计模式?
从已有的且证明有效的设计模式中获取灵感,少⾛弯路;
通⽤语⾔,知道在已有的设计模式扩展代码;
体会模式设计,设计⾃⼰的⾏之有效的设计模式;
学习设计模式的步骤
深刻体会上⾯的原则;
理解设计模式,能
知道设计模式的变化点和稳定点
;
能在已使⽤的设计模式中,知道如何写扩展;
能在复杂需求中,抽象出已有设计模式;
能在重构中,开发⾃⼰的设计模式;
能在重构中,开发⾃⼰的设计模式;
模板⽅法
定义
定义⼀个操作中的算法的⻣架 ,⽽将⼀些步骤延迟到⼦类中。
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
保护⼦类需要复写的⼦流程;这样⼦类的⼦流程只能⽗类来调⽤;
充分体现了依赖倒置原则;
本质
通过固定算法⻣架来约束⼦类的⾏为;
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/7174715289dc0e8cd2b7084853f788f4.png)
观察者模式
定义
定义对象间的⼀种⼀对多(变化)的依赖关系,以便当⼀个对象
(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
框架中;
本质
触发联动;
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/43e5ed6b2c92fca26aeec9e747d0c9ac.png)
策略模式
定义
定义⼀系列算法,把它们⼀个个封装起来,并且使它们可互相替换。该模式使得算法可独⽴于使⽤
它的客户程序⽽变化。
——
《设计模式》
GoF
背景
某商场节假⽇有固定促销活动,为了加⼤促销⼒度,现提升国庆节促销活动规格;
代码
要点
策略模式提供了⼀系列可重⽤的算法,从⽽可以使得类型在运⾏时⽅便地根据需要在各个算法
之间进⾏切换;
策略模式消除了条件判断语句;就是在解耦合;
充分体现了开闭原则;单⼀职责;
本质
分离算法,选择实现;
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/4c8864de2d53c03679f9f693eac5b694.png)
责任链模式
定义
使多个对象都有机会处理请求,从⽽避免请求的发送者和接收者之间的耦合关系。将这些对象连成
⼀条链,并沿着这条链传递请求,直到有⼀个对象处理它为⽌。
——
《设计模式》
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
调⽤责任链
要点
解耦请求⽅和处理⽅,请求⽅不知道请求是如何被处理,处理⽅的组成是由相互独⽴的⼦处理
构成,⼦处理流程通过链表的⽅式连接,⼦处理请求可以按任意顺序组合;
责任链请求强调请求最终由⼀个⼦处理流程处理;通过了各个⼦处理条件判断;
责任链扩展就是功能链,功能链强调的是,⼀个请求依次经由功能链中的⼦处理流程处理;
充分体现了单⼀职责原则;将职责以及职责顺序运⾏进⾏抽象,那么职责变化可以任意扩展,
同时职责顺序也可以任意扩展;
本质
分离职责,动态组合;
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/99fb4a0d6d4ff54d8ec87a6677f2cc64.png)
装饰器模式
定义
动态地给⼀个对象增加⼀些额外的职责。就增加功能⽽⾔,装饰器模式⽐⽣成⼦类更为灵活。
——
《设计模式》
GoF
背景
普通员⼯有销售奖⾦,累计奖⾦,部⻔经理除此之外还有团队奖⾦;后⾯可能会添加环⽐增⻓奖
⾦,同时可能针对不同的职位产⽣不同的奖⾦组合;
![](https://i-blog.csdnimg.cn/blog_migrate/c69e769b9c598a4088fbe7ac70f91eea.png)
代码
// 普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能产生不同的奖金组合;
// 销售奖金 = 当月销售额 * 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);
}
要点
通过采⽤组合⽽⾮继承的⼿法, 装饰器模式实现了在运⾏时动态扩展对象功能的能⼒,⽽且
可以根据需要扩展多个功能。 避免了使⽤继承带来的
“
灵活性差
”
和
“
多⼦类衍⽣问题
”
。不是解决“
多⼦类衍⽣的多继承
”
问题,⽽是解决
“
⽗类在多个⽅向上的扩展功能
”
问题;装饰器模式把⼀系列复杂的功能分散到每个装饰器当中,⼀般⼀个装饰器只实现⼀个功能,实现复⽤装饰器的功能;
本质
动态组合
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/aee0c3afbffab5ed7222b3a86799bb1c.png)
单例模式
定义
保证⼀个类仅有⼀个实例,并提供⼀个该实例的全局访问点。
——
《设计模式》
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&) {}
}
要点
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/f2df444b43bf45266366a333e392cef4.png)
策略模式
定义
定义⼀系列算法,把它们⼀个个封装起来,并且使它们可互相替换。该模式使得算法可独⽴于使⽤
它的客户程序⽽变化。
——
《设计模式》
GoF
背景
某商场节假⽇有固定促销活动,为了加⼤促销⼒度,现提升国庆节促销活动规格;
代码
要点
策略模式提供了⼀系列可重⽤的算法,从⽽可以使得类型在运⾏时⽅便地根据需要在各个算法
之间进⾏切换;
策略模式消除了条件判断语句;就是在解耦合;
充分体现了开闭原则;单⼀职责;
本质
分离算法,选择实现;
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/805e8bb014b15f247aaaa40bb48a08b2.png)
责任链模式
定义
使多个对象都有机会处理请求,从⽽避免请求的发送者和接收者之间的耦合关系。将这些对象连成
⼀条链,并沿着这条链传递请求,直到有⼀个对象处理它为⽌。
——
《设计模式》
GoF
背景
请假流程,
1
天内需要主程序批准,
3
天内需要项⽬经理批准,
3
天以上需要⽼板批准;
代码
nginx
阶段处理
//
严格意义说是功能链
// ngx_http_init_phase_handlers
初始化责任链
cmcf->phase_engine.handlers
// ngx_http_core_run_phases
调⽤责任链
要点
解耦请求⽅和处理⽅,请求⽅不知道请求是如何被处理,处理⽅的组成是由相互独⽴的⼦处理
构成,⼦处理流程通过链表的⽅式连接,⼦处理请求可以按任意顺序组合;
责任链请求强调请求最终由⼀个⼦处理流程处理;通过了各个⼦处理条件判断;
责任链扩展就是功能链,功能链强调的是,⼀个请求依次经由功能链中的⼦处理流程处理;
充分体现了单⼀职责原则;将职责以及职责顺序运⾏进⾏抽象,那么职责变化可以任意扩展,
同时职责顺序也可以任意扩展;
本质
分离职责,动态组合;
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/ca75767a96c32bb8187acd53e54378ed.png)
⼯⼚⽅法模式
定义
定义⼀个⽤于创建对象的接⼝,让⼦类决定实例化哪⼀个类。
Factory Method
使得⼀个类的实例化
延迟到⼦类。
——
《设计模式》
GoF
背景
实现⼀个导出数据的接⼝,让客户选择数据的导出⽅式;
代码
要点
解决
创建过程⽐较复杂
,希望对外隐藏这些细节;
⽐如连接池,线程池;
隐藏对象真实类型;
对象创建会有很多参数来决定如何创建;
创建对象有复杂的依赖关系;
本质
延迟到⼦类来选择实现;
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/f7f99359f30df5d8de13d7cc463b336e.png)
抽象⼯⼚模式
定义
提供⼀个接⼝,让该接⼝负责创建⼀系列
“
相关或者相互依赖的对象
”
,⽆需指定它们具体的类。
——
《设计模式》
GoF
背景
实现⼀个拥有导出导⼊数据的接⼝,让客户选择数据的导出导⼊⽅式;
代码
要点
本质
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/b1a298fb10de28b3273427d6ebc0a977.png)
适配器模式
定义
将⼀个类的接⼝转换成客户希望的另⼀个接⼝。
Adapter
模式使得原本由于接⼝不兼容⽽不能⼀起
⼯作的那些类可以⼀起⼯作。
——
《设计模式》
GoF
背景
⽇志系统,原来是通过写磁盘的⽅式进⾏存储,后来因为查询不便,需要额外添加往数据库写⽇志
的功能(写⽂件和数据库并存);
代码
要点
原来的接⼝是稳定的,新的外来的需求是变化的,那么可以通过继承原来的接⼝,让原来的接
⼝继续保持稳定,在⼦类通过组合的⽅式来扩展功能;
本质
转换匹配,复⽤功能;
结构图
代理模式
定义
为其他对象提供⼀种代理以控制对这对象的访问。
——
《设计模式》
GoF
背景
在有些系统中,为了某些对象的纯粹性,只进⾏了功能相关封装(稳定点),后期添加了其他功能
需要对该对象进⾏额外操作(变化点),为了隔离变化点(也就是不直接在稳定点进⾏修改,这样
会让稳定点也变得不稳定),可以抽象⼀层代理层;
代码
要点
远程代理(隐藏⼀个对象存在不同的地址空间的事实),虚代理(延迟加载
lazyload
),保护
代理(在代理前后做额外操作,权限管理,引⽤计数等);
在分布式系统中,
actor
模型(
skynet
)等常⽤的设计模式;
本质
控制对象访问;
结构图
![](https://i-blog.csdnimg.cn/blog_migrate/613f45025a67f2cbc49180a411060ba0.png)