设计模式:创建型
说明:本文内容主要摘自www.0voice.com课程内容
设计模式定义
设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案
如何学习设计模式?
- 找到稳定点和变化点,把变化点隔离出来(解耦合-把耦合限定在最小范围)
- 先满足设计原则,慢慢迭代出设计模式(分治思维)
设计原则
依赖倒置
- 高层模块不应该依赖低层模块,两者都应该依赖抽象;
- 抽象不应该依赖具体实现,具体实现应该依赖于抽象;
High level modules should not depend upon low level modules. Both should depend upon abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.
– Robert C.Martin

自动驾驶系统作为高层不应依赖于底层的具体汽车厂商,而应该抽象出一个自动驾驶标准,让高层和低层模块都依赖于它;同时自动驾驶系统、汽车生产厂商都是具体实现,它们应该都依赖自动驾驶行业标准(抽象)。
开放封闭
一个类应该对扩展开放,对修改关闭。
类的直接组合和继承增强了类之间的耦合。应使用的拓展方法:对于C++,尽可能组合类的指针,而不是类本身(晚绑定/动态绑定);或者使用虚函数进行继承。
面向接口
客户程序应该只知道对象接口,而不需要知道对象的具体数据类型
封装变化点
将稳定点和变化点分离,扩展修改变化点;让稳定点和变化点的实现层次分离
单一职责
一个类应该仅有一个引起它变化的原因
理氏替换
子类型必须能够替换掉它的父类型,具体地说就是,子类覆盖父类时必须要实现父类的原则;
接口隔离
不应该强迫客户依赖于它们不用的方法;
一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责。
组合优于继承
继承耦合度高,组合耦合度低
原则的层次关系
优先满足这四个原则:
- 开放封闭(最优先)
- 单一职责
- 理氏替换
- 接口隔离
后面三个间接地佐证开闭原则:单一职责->修改封闭;理氏替换->扩展开放;接口隔离->减少修改
根据这四个原则,慢慢地就能推导出设计模式。
模板方法
定义
定义一个操作中的算法的骨架 ,而将一些步骤的实现延迟到子类中。 Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 ——《 设计模式》 GoF
要点
- 最常用的设计模式,子类可以复写父类子流程,使父类的骨架流程丰富;
- 反向控制流程的典型应用(父类流程控制子类流程);
- 父类 protected 保护子类需要复写的子流程;这样子类的子流程只能父类来调用。
举例
某个品牌动物园,有一套固定的表演流程,但是其中有若干个表演子流程可创新替换,以尝试迭代更新表演流程
这里头一套固定的表演流程就是算法(父类)的骨架(固定点),而若干个表演子流程(变化点)就是父类的一些可以复写的方法。
本质
通过固定算法骨架来约束子类的行为。
结构图

代码示例
对于上面提到的动物园表演流程实现代码,这里有一个不好的例子:
class ZooShow {
public:
ZooShow(int type = 1) : _type(type) {}
public:
void Show() {
Show0();
Show1();
Show2();
Show3();
}
private:
void Show0() {
cout << _type << " show0" << endl;
}
void Show1() {
if (_type == 1) {
cout << _type << " Show1" << endl;
} else if (_type == 2) {
cout << _type << " Show1" << endl;
} else if (_type == 3) {
}
}
void Show2() {
if (_type == 20) {
}
cout << "base Show2" << endl;
}
void Show3() {
if (_type == 1) {
cout << _type << " Show1" << endl;
} else if (_type == 2) {
cout << _type << " Show1" << endl;
}
}
private:
int _type;
};
不难看出来,这段代码在showx()等方法中通过if-else来实现表演子流程的替换,导致代码臃肿,难以维护,每次替换子流程都要修改父类,显然违反了开放封闭原则。同时,ZooShow()中_type的引入导致类出现多个变化方向,不满足单一职责原则。
运用模板方法之后的代码如下:
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;
}
};
其中,Show() 函数就是父类的固定骨架,ZooShowEx 等子类继承父类的骨架后可以任意修改其protected属性的子流程。同时,将各个Showx()函数设置为protected也符合接口隔离原则。
观察者模式
定义
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。 ——《 设计模式》 GoF
要点
- 观察者模式使得我们可以独立地改变目标与观察者,从而使二者之间的关系松耦合;
- 观察者自己决定是否订阅通知,目标对象并不关注谁订阅了;
- 观察者不要依赖通知顺序,目标对象也不知道通知顺序;
常用场景:
- 常用在基于事件的ui框架中,也是 MVC 的组成部分;
- 常用在分布式系统中、actor框架中;
举例
气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到两个不同的显示终端(A和B)
本质
触发联动 - 目标对象一触发变化,就主动联动观察者。
结构图

代码示例
- 首先定义观察者
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; // 保存注册的观察者
};
补充:分布式锁-互斥锁(公平锁)和自旋锁(非公平锁)
公平锁的工作方式可以理解为一种发布订阅。
重IO操作必须要互斥锁;轻量级的操作使用自旋锁,避免进程切换带来额外开销。
策略模式
定义
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。
该模式使得算法可独立于使用它的客户程序而变化。 ——《设计模式》 GoF
要点
- 策略模式提供了一系列可重用的算法,从而可以使得类型在运⾏时方便地根据需要在各个算法之间进行切换;
- 策略模式消除了条件判断语句;也就是在解耦合;(实际上是把if-else放到稳定点,避免变化点受大量if-else影响)
举例
某商场节假日有固定促销活动,为了加大促销力度,现提升国庆节促销活动规格
各种节假日就是稳定点,而促销活动是变化点。
本质
分离算法,选择实现
结构图

代码示例
- 首先节假日是属于稳定点。
class Promotion { // 稳定的
public:
Promotion(ProStategy *sss) : s(sss){}
~Promotion(){}
double CalcPromotion(const Context &ctx){
return s->CalcPro(ctx);
}
private:
ProStategy *s; // 变化的
};
促销策略(具体算法)是变化的,因此单独抽象出来。
class Context {
};
class ProStategy {
public:
virtual double CalcPro(const Context &ctx) = 0;
virtual ~ProStategy();
};
// cpp
class VAC_Spring : public ProStategy {
public:
virtual double CalcPro(const Context &ctx){}
};
// cpp
class VAC_QiXi : public ProStategy {
public:
virtual double CalcPro(const Context &ctx){}
};
class VAC_QiXi1 : public VAC_QiXi {
public:
virtual double CalcPro(const Context &ctx){}
};
// cpp
class VAC_Wuyi : public ProStategy {
public:
virtual double CalcPro(const Context &ctx){}
};
// cpp
class VAC_GuoQing : public ProStategy {
public:
virtual double CalcPro(const Context &ctx){}
};
class VAC_Shengdan : public ProStategy {
public:
virtual double CalcPro(const Context &ctx){}
};
本文深入探讨了C/C++后端开发中的设计模式,包括模板方法、观察者模式和策略模式,强调了设计原则如依赖倒置、开放封闭、面向接口等。文章通过实例解释了如何在实际开发中应用这些模式,以提高代码的可维护性和扩展性。

1093

被折叠的 条评论
为什么被折叠?



