网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
如上版本单例模式的优势所在
- 利用静态局部变量特性,延迟加载
- 利用静态局部变量的特性, 系统自动回收内存,自动调用析构
- 静态局部变量的初始化没有new操作带来的CPU指令reorder问题
- C++静态局部变量的初始化, 自带线程安全
这种单例设计模式是否已经无敌了,完美了,看上去好像是这样的。但是还是存在这样一个问题,一旦需要继承这个单例类,应该如何书写??? 才可以使得可以通过继承的方式创建仍以具体类的单例对象
template<class T >
class Singleton {
public:
static T& GetInstance() {
static T _instance;//定义并且初始化单例对象
return _instance;//return 单例对象
}
protected:
//一旦继承析构函数一定需要进行虚析构
virtual ~Singleton() {
cout << "析构单例对象" << endl;
}
Singleton() {
cout << "构造单例对象" << endl;
}
Singleton(const Singleton&) {}//隐藏拷贝构造
Singleton& operator= (const Singleton&) {}//隐藏赋值重载
};
//继承书写, 通过如此形式实现继承单例类的书写,
//如此可以实现了任意类型的单例类实例化
class DesignPattern : public Singleton<DesignPattern> {
friend class Singleton<DesignPattern>;
//声明为友元, 使得Singleon<DesignPattern>类可以访问private成员
private:
DesignPattern() {
cout << "调用DesignPattern构造" << endl;
}//私有化构造函数
DesignPattern& operator=(const DesignPattern&) {}//私有化赋值重载
DesignPattern(const DesignPattern&){}//私有化拷贝构造
virtual ~DesignPattern() {
cout << "调用DesignPattern析构" << endl;
}
};
观察者设计模式
官方定义: 定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。 ——《 设计模式》 GoF
口语解释: 说白了就是定义一个类对象跟多个其他类对象之间的一种依赖关系, 其他多个类对象依赖于这一个类对象. 一旦这一个类对象状态发生了改变. 其他依赖这个对象的所有其他类对象(观察者) 都会得到通知进行自动更新变化
通知依赖关系——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。
怎么说:这个强依赖关系, 过于的耦合了, 我们需要实现运行时绑定, 晚绑定, 依赖抽象,依赖倒置来解除这个对具体对象的强依赖. 改为对抽象的依赖.
我们还是通过前后对比书写的方式来重构研究一下具体缺乏哪些设计原则, 添加哪些设计原则重构最终实现了观察者模式
场景引入: 比如说存在这样一个场景,正常情况下,大家上班都在进行摸鱼防水,轻松, 但是大家也都是很聪明的存在, 是懂得观察的存在. 要是老板来了,大家就会立刻Update更新当前的状态,所作的事情.
— 此时的所有Wocker 或者说Staff 全部都是监视者. 而老板就是这个被监视的对象,老板的状态改变就会引起所有监视者的状态改变
class Staff;
class Boss {
private:
string action;
public:
Boss(string _action): action(_action) {}
string& GetAction() {
return action;
}
};
class Staff {
private:
string name;
Boss boss;
public:
Staff(string _name, Boss _boss)
: name(_name)
, boss(_boss) {
}
void Run() {
if (boss.GetAction() == "老板从大门走进来了") {
cout <<"name: " << name << "老板来了,"<< "立刻马上认真工作" << endl;
}
else if (boss.GetAction() == "老板走出去了") {
cout <<"name: " << name << "老板走了,"<< "立马开始摸鱼起来了" << endl;
}
}
};
int main() {
Staff st("张三", Boss("老板走出去了"));
st.Run();
Staff st2("李四", Boss("老板从大门走进来了"));
st2.Run();
return 0;
}
如上代码可行不可行, 其实也可以实现功能. 老板回来了,出去了老板的状态可以影响到它的不同员工的状态.
但是上述代码用一句名人说的话就是 bad smell. 嗅到了坏味道. why?
员工类高度耦合依赖Boss老板类, 一旦老板类的状态发生任何变化都可能导致员工类无法正常使用, 另外没有遵循设计原则. 不遵循依赖倒置, 是编译时绑定依赖, 而不是运行时依赖. 毫无扩展性可言, 比如说需要通知一下其他的事情, 状态,便需要修改类. 而不是扩展类. 没有做到封装可能的变化点
改进代码如下:
class Observer {
public:
//Subject目标变化带来的Update
virtual void Update(int) = 0;
protected:
virtual ~Observer() {}
};
//目标,被观察者
class Subject {
public:
void Attach(Observer* pob) {
pobs.push_back(pob);
}
void Detach(Observer* pob) {
auto it = find(pobs.begin(), pobs.end(), pob);
if (it != pobs.end()) {
it = pobs.erase(it);
}
}
void Notify() {
auto it = pobs.begin();
while (it != pobs.end()) {
(*it)->Update(action);
++it;
}
}
virtual void SetAction(int action) = 0;
protected:
int action;
vector<Observer*> pobs;
virtual ~Subject(){}
};
class Boss : public Subject {
public:
//action 0 表示老板离开, 1 表示老板进入
virtual void SetAction(int action) {
this->action = action;
Notify();
}
};
class Staff : public Observer {
public:
virtual void Update(int action) {
if (action == 0) {
cout << "老板走了大家可以划水了" << endl;
}
if (action == 1) {
cout << "老板进来了, 大家快好好工作" << endl;
}
}
};
对比前后,上述设计模式采用了哪些设计原则以及有哪些特征
- 开闭原则, 对扩展开放, 对修改封闭
- 依赖倒置原则, 都不是依赖具体的类, 而是依赖抽象, 将编译时依赖变成了运行时依赖 (解决了具体类之间的强耦合性) — 封装变化点
- 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
- 目标对象不需要知道谁订阅了自己,也就是不需要知道哪些对象在观察自己
- 观察者自己决定是否订阅通知,目标对象并不关注谁订阅了
- 在目标对象状态改变的时候通知观察者的顺序不固定, 随机
- Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
观察者模式的特点总结
- 观察者模式使得我们可以独立地改变目标与观察者,从而使二者之间的关系松耦合
- 观察者自己决定是否订阅通知,目标对象也不关系谁订阅了
观察者模式的记忆技巧
一个对象状态变化引发的联动现象 ---- 状态改变, 触发联动
策略模式
官方定义: 定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。 ——《设计模式》 GoF
口语解释:封装算法流程. 各个算法之间是等效的, 抽象算法特征进行扩展, 核心关键在于使得算法可以独立于客户程序变化 (解耦合:编译时依赖转换为运行时依赖.)
有那么一点子特别像是工厂模式。只不过工厂模式是创建对象的创建模式. 而这个策略模式是创建算法,创建策略, context是提供上下文, 选取策略的类
首先先写一份拉跨的代码, 没有策略的代码
场景引入: 收取税收, 对于不同国家按照不同的税收策略算法进行收税
enum TaxType {
CN_Tax,
US_Tax,
USA_Tax,
FR_Tax
};
class TaxClass {
TaxType tax;
public:
double CalculateTax() {
//...
if (tax == CN_Tax) {
//...一种税收算法
}
else if (tax == US_Tax) {
//... 另一种税收算法
}
else if (tax == USA_Tax) {
//... 再一种税收算法
}
else if (tax == FR_Tax) {
//... 再再一种税收算法
}
//...
}
};
分析弊端:
- 职责众多, 不满足单一职责的设计原则
- 不满足开放封闭原则,类的扩展性极差, 如果CalculateTax是稳定的, 固定不变的模块, 这个使用if else是没啥问题的, 比如说 if (男) else (女) 这种固定的不会扩展的可以这样写. 但是对别国的关税税收这个完全是不确定的事情. 税收的计算算法也是一个复杂的流程, 新增税收以及更改税收算法都需要直接修改TaxClass税收类
- 众多职责, 复杂的算法耦合在一个Tax类中,使得TaxClass类变得复杂,对于不适用的算法的判断也是一种性能负担
- 耦合度过高, 算法和Tax类的耦合度过高, 如何消除众多的if条件判断,解除耦合性
class Context;//提供上下文, 用于计算
//抽象税收策略类
class TaxStrategy {
public:
virtual double CalculateTax(const Context& ctx) = 0;
virtual ~TaxStrategy() {}
};
//扩展具体的税收策略
class CNTax : public TaxStrategy {
public:
virtual double CalculateTax(const Context& ctx) {
//...
}
};
class USTax : public TaxStrategy {
public:
virtual double CalculateTax(const Context& ctx) {
//...
}
};
class USATax : public TaxStrategy {
public:
virtual double CalculateTax(cosnt Context& ctx) {
//...
}
};
class TaxClass {
private:
TaxStrategy* strategy;//strategy 策略指针, 指向一个具体的策略.
//利用晚绑定实现运行时依赖具体的strategy
public:
double Calculate() {
Context ctx;
return strategy->CalculateTax(ctx);
}
};
对比前后添加的设计原则:
- 开放封闭原则, 对于修改封闭了,而对扩展开放了.
- 单一职责原则, 很明显的将大量的算法分离除去了,而不是像之前耦合在一个类中. 使得一个具体的策略类专注于实现一个算法, 职责单一
- 解除了耦合性, 将算法和对象解耦合, 不再依赖于众多的if 判断选择算法
- Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
最后再谈一谈什么时候使用策略设计模式吧?
不是说看见了if else if就一定需要使用策略模式. 简单固定的几个if else if判断是没有必要使用策略模式的,策略模式的使用更多的是为了便于将来的扩展 , 如果你对于需求的熟悉,明显的知道将来的扩展方向,那你当然可以将其设计为策略模式呀. 利于将来的扩展. — 或者一开始很急,先简单的使用if else if 实现需求,之后再重构为策略模式
策略模式记忆核心一句话:分离算法,抽象策略,选择生产策略
感觉策略的生产可以采取工厂模式,感兴趣的可以拓展一下.
责任链设计模式(数据结构模式)
官方定义: 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有⼀个对象处理它为止。 ——《设计模式》GoF
通俗解释: 使用一条链将所有的处理方法的对象链接起来, 一旦有请求到来, 仅仅只需要将请求交给责任链中即可, 具体是哪个对象处理的这个请求, 我不需要关心, 能不能处理完成也不关心…
----将请求对象跟具体的处理对象解耦合. 解除依赖. 请求对象不需要绑定依赖于具体的处理对象了, 像极了队列缓冲区,解耦合的思想, 异步解耦合等这种思想.
场景引入: 请假流程,1天内需要主程序批准,3天内需要项目经理批准,3天以上需要老板批准
首先是第一款实现:
//提供上下文
class Context {
public:
string name;
int day;
};
//请假请求
class LeaveRequest {
public:
//按照上下文处理请求
bool HandleRequest(const Context& ctx) {
if (ctx.day <= 1) {
HandleByMainProgram(ctx);
}
else if (ctx.day <= 3) {
HandleByProMgr(ctx);
}
else {
HandleByBoss(ctx);
}
}
private:
bool HandleByMainProgram(const Context& ctx) {//主程序处理
}
bool HandleByProMgr(const Context& ctx) {//项目主管处理
}
bool HandleByBoss(const Context& ctx) {//老板处理
}
};
上述代码的问题何在? LeaveRequest类的不稳定性, 如果其中的判断增多就需要再新增处理. 耦合度高. Contex类需要知道LeaveRequest类对象是否可以处理自己的请求. 而且Contex类对象高度依赖于LeaveRequest类对象, leaveRequest也同样依赖于Contex对象。
这样的请求发送和接受者之间的高度耦合,不符合设计原则. 所以我们需要对请求的处理进行抽象, 以及通过链接数据结构的方式解除耦合性.
(如何理解使用链表,链接不同的处理,形成职责链可以解除耦合性? 有了职责链, 我请求者不需要确定知晓处理请求的对象, 仅仅只需要将自己的需求抛入到职责链即可. 同样职责链也不要知晓有哪些对象可以向自己进行请求. 我只管处理请求)
//提供上下文
class Context {
public:
string name;
int day;
};
//抽象责任链, 抽象接口. 稳定类.
//不稳定的变化的接口交由子类重写
class IHandler {
public:
void SetNextHandler(IHandler* _next) {
next = _next;
}
//Handle是一个稳定的算法骨架子, TemplateMethod
bool Handle(const Context& ctx) {
if (CanHandle(ctx)) {
HandleRequest(ctx);
}
else if (GetNextHandler()) {
return GetNextHandler()->HandleRequest(ctx);
}
else {
//err 出错了,链到底了都没有处理
cerr << "责任链无法处理" << endl;
}
}
protected:
virtual bool CanHandle(const Context& ctx) = 0;//能不能handle也交由子类决定
virtual bool HandleRequest(const Context& ctx) = 0;//具体的handle方法由子类实现
IHandler* GetNextHandler() {
return next;
}
virtual ~IHandler() {}
private:
IHandler* next;
};
//继承重写扩展出具体的请求处理类
class HandleByMainProgram : public IHandler {
protected:
virtual bool HandleRequest(const Context& ctx) {
//
return true;
}
virtual bool CanHandle(const Context& ctx) {
//
return true;
}
};
class HandleByProjMgr : public IHandler {
protected:
virtual bool HandleRequest(const Context& ctx) {
//
return true;
}
virtual bool CanHandle(const Context& ctx) {
//
return true;
}
};
class HandleByBoss : public IHandler {
protected:
virtual bool HandleRequest(const Context& ctx) {
//
return true;
}
virtual bool CanHandle(const Context& ctx) {
//
return true;
}
};
int main() {
//使用方式
IHandler* h1 = new HandleByMainProgram();
IHandler* h2 = new HandleByProjMgr();
IHandler* h3 = new HandleByBoss();
h1->SetNextHandler(h2);
h2->SetNextHandler(h3);
// 设置下一指针
Context ctx;
h1->Handle(ctx);
return 0;
}
对比前后优势何在, 运用了哪些设计原则在其中?
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
r();
IHandler* h3 = new HandleByBoss();
h1->SetNextHandler(h2);
h2->SetNextHandler(h3);
// 设置下一指针
Context ctx;
h1->Handle(ctx);
return 0;
}
**对比前后优势何在, 运用了哪些设计原则在其中?**
[外链图片转存中...(img-mHm0Jarp-1715826095972)]
[外链图片转存中...(img-HFbeghPa-1715826095973)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**