设计模式

《总之,好记性不如烂笔头!把你遗忘的都记下来吧!》

UML类图

img

img

img

  • 静态属性/方法:加上下划线,如 -ClassAttribute: Long

  • 抽象属性/方法:使用斜体表示,如 +AbstractOperation()

  • 职责:可以在操作部分的下面再添加一个区域,用来说明类的职责,即说明类或其它元素的契约或义务。

  • 约束:指定了该类所要满足的一个或多个规则,用大括号表示,如 {Some Properties}

image-20240629171715820

继承关系

img
1
2
class Vehicle{};
class Truck : public Vehicle{};

实现关系

imgimage-20240629233149588

1
2
3
4
5
6
7
8
9
10
11
class IUser{
public:
virtual void walk() = 0;
virtual ~walk(){}
};
class VipUser : public IUser{
public:
virtual void walk() override{
std::cout<<"do walk"<<std::endl;
}
};

依赖关系

img
1
2
3
4
5
6
7
8
class Car{
public:
void Move(){};
};
class Person{
public:
void Travel(Car &car){ car.Move(); }
};

关联关系

img
1
2
3
4
class Department{};
class Teacher{
Department depart;
};
img
1
2
3
4
5
6
7
class Teacher{
public:
Student aLearner;
};
class Student{
Teacher alnstructor;
};
img
1

聚合关系

聚合关联:

img
1
2
3
4
5
6
7
class University{
public:
std::list<College> colleges;
};
class College{
University university;
};

单向聚合:img

1
2
3
4
class College{};
class University{
std::list<College> colleges;
};

组合关系

img
1
2
3
4
5
6
7
8
9
class Side{};
class Triangle{
public:
void addSide(const Side& side){
m_sides.push_back(side);
}
private:
std::list<Side> m_sides;
}

设计模式

可复用面向对象软件基础

底层思维:将机器底层从微观理解对象构造

抽象思维:向上将世界逻辑抽象为代码

解决变化

结构化和面向对象

解决问题的复杂性:

1、分解,将复杂的行为抽象成每一部分,但是应对改变需要改变较多的代码

2、抽象,将行为抽象,使用一种通用的方法统一处理,应对变化,处理方法不变,复用性高

软件设计的目标是:复用。

设计原则

image-20240628225820577

重新认识面向对象

隔离变化:从宏观层面,面向对象构建能够将变化带来的影响减少到最小。

各司其职:从微观层面,面向对象的方式更强调类的责任,新增加的类不应该影响原来的类型的实现。

对象是什么?

从语言层面,对象封装数据和方法;

从规格层面,对象是一系列可以被使用的公告接口;

从概念层面,对象是某种拥有责任的抽象。

1、*依赖倒置原则:

高层模块(稳定)不应该依赖于底层模块(变化),二者都应依赖于抽象

抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)

在结构化的程序中,常常让上层模块调用下层模块,

在面向对象的程序设计中,我们应该让抽象程度更高的模块被抽象程度低的模块依赖。

image-20240628222829360

2、开放封闭原则:

对拓展开放,对更改封闭,类模块应该是可拓展的,但是是不可以修改的。

3、单一职责原则:

一个类应该仅有一个引起它变化的原因。变化的方向隐含着类的责任

防止类过于臃肿。

4、里氏替换原则:

子类应该能完全充当一个货真价实的父类对象。继承表达类型抽象。

可用改进代码段中存在的大量if else可以用循环替代,引入抽象类

5、接口隔离原则:

接口应该小而完备

不应该强迫客户程序依赖它们不用的方法。

6、优先使用对象组合,而不是类继承:

类继承通常为”白箱复用“,对象组合为”黑箱复用“

继承在某种程度上破坏了封装性,子类父类耦合性高

对象组合只要求被组合的对象的对象有良好的接口定义,耦合度低。

7、封装变化点:

使用封装来创建对象之间的分界层,让设计者在分界层一侧修改,不产生另一侧的影响。

8、针对接口编程,而不是针对实现编程:

不将变量类型声明为某个特定的具体类,而是声明称具体的接口。

客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。

减少系统各部分的依赖关系,实现”高内聚,低耦合“

迪米特法则:

一个类应该和尽可能少的类发生关联

设计模式不易先入为主,开始就使用设计模式是对设计模式的最大误用。没有一步到位的设计模式,敏捷软件开发提倡”Refactoring to Patterns” 重构到设计模式

*重构的关键技法

静态->动态

早绑定->晚绑定

继承->组合

编译时依赖->运行时依赖

紧耦合->松耦合

创建型模式

通过对象创建模式绕开new,来避免对象创建new过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象后的第一步工作。

工厂模式

image-20240630112241303

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 工厂方法模式
#include <iostream>
class Product{
public:
virtual ~Product(){}
// 定义纯虚函数,必须在子类中覆盖
virtual std::string Operation() const = 0;
};
class ConcreteProduct1 : public Product{
public:
std::string Operation() const override{
return "{ Result of the ConcreteProduct1 }";
}
};
class ConcreteProduct2 : public Product{
public:
std::string Operation() const override{
return "{ Result of the ConcreteProduct2 }";
}
};
class Creator{
public:
virtual ~Creator(){}
// 创建者不提供具体的产品构建方法,只是提供了一个可以重写的纯虚函数
virtual Product *FactoryMethod() const = 0;
// 对不同的产品执行对应的操作
std::string SomeOperation() const{
// 构建对应的产品对象,使用多态,函数表指向对应的虚函数
// 利用了两次多态,第一次是构建产品对象,第二次是使用产品对象的方法
Product *product = this->FactoryMethod();
std::string result =
"Creator: The same creator's code has just worked with " +
product->Operation();
delete product;
return result;
}
};
class ConcreteCreator1 : public Creator{
public:
// 创建产品1
Product *FactoryMethod() const override { return new ConcreteProduct1(); }
};
class ConcreteCreator2 : public Creator{
public:
// 创建产品2
Product *FactoryMethod() const override { return new ConcreteProduct2(); }
};

void ClientCode(const Creator& creator){
std::cout << "Client : IM not aware of the creator's class, but it still "
"works.\n"
<< creator.SomeOperation() << std::endl;
}
int main(int argc, char const *argv[])
{
std::cout << "1" << std::endl;
Creator *creator = new ConcreteCreator1();
ClientCode(*creator);
std::cout << std::endl;
Creator *creator2 = new ConcreteCreator2();
ClientCode(*creator2);

return 0;
}

工厂模式用来隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系导致软件脆弱。

将要创建的具体对象的工作延迟到子类。

抽象工厂模式

在软件系统中,经常面临一系列相互依赖的对象的创建工作,同时需求的变化,往往存在更多系列对象的创建工作。

提供一个接口,让该接口负责创建一系列相关或者相互依赖的对象,无需指定它们具体的类。

image-20240630154505578

单例模式

局部静态变量实现单例

1
2
3
4
5
6
7
8
9
10
11
12
13
// 局部静态变量实现单例类 c11之前是线程不安全的
class SingleInstance{
private:
SingleInstance(){}
SingleInstance(const SingleInstance &) = delete;
SingleInstance& operator=(SingleInstance &) = delete;

public:
static SingleInstance& GetInstance(){
static SingleInstance single;
return single;
}
};

饿汉模式的单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 饿汉模式实现单例
class SingleHungryInstance {
private:
SingleHungryInstance(){}
SingleHungryInstance(const SingleHungryInstance &) = delete;
SingleHungryInstance &operator=(SingleHungryInstance &) = delete;
public:
static SingleHungryInstance* GetInstance(){
if(single == nullptr){
single = new SingleHungryInstance;
}
return single;
}
private:
static SingleHungryInstance *single;
};
// 初始化类静态成员变量
SingleHungryInstance* SingleHungryInstance::single = new SingleHungryInstance;

懒汉模式的单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class SingleLazyInstance {
private:
SingleLazyInstance(){}
SingleLazyInstance(const SingleLazyInstance &) = delete;
SingleLazyInstance &operator=(SingleLazyInstance &) = delete;
public:
static SingleLazyInstance* GetInstance(){
if(single != nullptr){
return single;
}
m_mtx.lock();
// 双检查锁
if(single != nullptr){
m_mtx.unlock();
return single;
}
single = new SingleLazyInstance();
m_mtx.unlock();
return single;
}

private:
static SingleLazyInstance *single;
static std::mutex m_mtx;
};
// 静态成员变量不要忘记类外初始化
SingleLazyInstance *SingleLazyInstance::single = nullptr;
std::mutex SingleLazyInstance::m_mtx;

std::call_once的单例

在单例模式下使用,std::once_flag实例对应一次不同的初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用std::call_once的单例模式
class SingleLazyInstance2 {
private:
SingleLazyInstance2(){}
SingleLazyInstance2(const SingleLazyInstance2 &) = delete;
SingleLazyInstance2 &operator=(SingleLazyInstance2 &) = delete;
public:
static SingleLazyInstance2* GetInstance(){
// 使用std::call_once确保single只被初始化一次
std::call_once(initFlag, []() {
single = new SingleLazyInstance2;
});
return single;
}
private:
static SingleLazyInstance2 *single;
static std::once_flag initFlag;
};
// 静态成员变量不要忘记类外初始化
SingleLazyInstance2 *SingleLazyInstance2::single = nullptr;
std::once_flag SingleLazyInstance2::initFlag;

使用std::call_once实现的单例模板类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T> 
class Singleton {
protected:
Singleton(){}
virtual ~Singleton(){}
Singleton(const Singleton<T> &) = delete;
Singleton<T> &operator=(const Singleton<T> &) = delete;

public:
static T& GetInsance(){
static T instance;
return instance;
}
};

原型模式

有一个对象,希望产生与其完全相同的复制品

结构型模式

单一职责类:

1、装饰模式

image-20240629145354687

image-20240629145411371

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//业务操作
class Stream{
public
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主体类
class FileStream: public Stream{
public:
virtual char Read(int number){
//读文件流
}
virtual void Seek(int position){
//定位文件流
}
virtual void Write(char data){
//写文件流
}
};
class NetworkStream :public Stream{};
class MemoryStream :public Stream{};

//扩展操作
class CryptoStream: public Stream {
// 通过聚合来实现装饰器
Stream* stream;//...
public:
CryptoStream(Stream* stm):stream(stm){
}
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
stream::Seek(position);//定位文件流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
stream::Write(data);//写文件流
//额外的加密操作...
}
};
class BufferedStream : public Stream{
Stream* stream;//...
public:
BufferedStream(Stream* stm):stream(stm){ }
//...
};
void Process(){
//运行时装配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}

2、桥模式

将一个大类或者紧密相关的类拆分为抽象和实现两个独立的层次结构,将抽象部分(业务功能)和实现部分(平台实现)分离,都可以独立地变化。

行为模式

组件协作类:

组件协作模式通过晚绑定,实现框架与应用程序的松耦合

1、模板方法

定义一个操作中算法的骨架(稳定),而是将一些步骤延迟(变化)到子类中。Template Method使得子类可以不改变(复用)一个算法的结构,即可重定义(override)该算法的特定步骤。

image-20240628235413653

*不论是在哪个设计模式中,在类图中应该指出稳定的部分,和变化的部分来便于理解

拓展:子类继承父类,对虚函数进行override。

内含反向的调用结构,不要调用我,让我来调用你。

推荐将被Template Method调用的虚方法成为protected的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
class library {
public:
void run(){
step1();
if(step2()){
step3();
}
for(int i = 0; i < 4; ++i) {
step4();
}
step5();
}
virtual ~library() { std::cout << "library is destory" << std::endl; }
protected:
void step1() { std::cout << "step 1" << std::endl; }
void step3() { std::cout << "step 3" << std::endl; }
void step5() { std::cout << "step 5" << std::endl; }
virtual bool step2() = 0; // 变化
virtual void step4() = 0;
private:
};
class application: public library{
public:
~application() { std::cout << "application is destory" << std::endl; }

protected:
bool step2() override {
std::cout << "step 2" << std::endl;
return true;
}
void step4() override { std::cout << "step 4" << std::endl; }
};

int main(int argc, char const *argv[])
{
library *lib = new application;
lib->run();
delete lib;
return 0;
}

2、策略模式

定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 当存在大量if else选择执行
enum TaxBase {
CN_Tax,US_Tax,DE_Tax,
FR_Tax//改变
};
class SalesOrder{
TaxBase tax;
public:
double CalculateTax(){
//...
if (tax == CN_Tax){
//CN***********
}
else if (tax == US_Tax){
//US***********
}
else if (tax == DE_Tax){
//DE***********
}
else if (tax == FR_Tax){ //改变
//...
}
//....
}
};

使用策略模式更改这些if else选项

image-20240629223222091

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
class Context {};
class TaxStrategy{
public:
virtual double Calculate(const Context &context) = 0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy {
public:
virtual double Calculate(const Context &context){
std::cout << "do CN tax" << std::endl;
return 1.0f;
}
};
class NATax : public TaxStrategy{
public:
virtual double Calculate(const Context &context){
std::cout << "do NA tax" << std::endl;
return 2.0f;
}
};
class SalesOrder{
public:
SalesOrder(TaxStrategy *taxStrategy) // 此处可以使用工厂方法构造
:strategy(taxStrategy){}

double CalculateTax(){
Context context;
double val = strategy->Calculate(context);
return val;
}
~SalesOrder(){
delete strategy;
}
private:
TaxStrategy *strategy;
};
int main(int argc, char const *argv[])
{
TaxStrategy *tax = new NATax;
SalesOrder sales(tax);
sales.CalculateTax();
return 0;
}

Strategy及其子类为组件提供了一系列可以重用的算法,从而使类型在运行时方便的在各个算法中进行转化。

Strategy模式提供条件判断语句的另一种选择,消除条件判断就是在解耦合。

如果Strategy对象没有实例变量,那么上下文可以共享同一个Strategy对象,节约对象开销。

3、观察者/事件模式

定义对象间一种对多(变化)的依赖关系,以便当一个对象的状态发生改变时,所有依赖它的对象都能够得到通知并自动更新

image-20240629135852220

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <list>
// --------------------Observer
class IProgress{
public:
virtual void DoProgress(float value)=0;
virtual ~IProgress(){}
};
// --------------------Subject
class FileSplitter
{
std::string m_filePath;
int m_fileNumber;
std::list<IProgress*> m_iprogressList; // 抽象通知机制,支持多个观察者
public:
FileSplitter(const std::string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber){
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress(progressValue);//发送通知
}
}
void addIProgress(IProgress* iprogress){
m_iprogressList.push_back(iprogress);
}
void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}
protected:
// 通知所有的观察者
virtual void onProgress(float value){
std::list<IProgress*>::iterator itor=m_iprogressList.begin();
while (itor != m_iprogressList.end() ){
(*itor)->DoProgress(value); //更新进度条
itor++;
}
}
};
// --------------------ConcreteObserver
class MainForm : public Form, public IProgress
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;

public:
void Button1_Click(){
std::string filePath = txtFilePath->getText();
int number = std::atoi(txtFileNumber->getText().c_str());
ConsoleNotifier cn;
FileSplitter splitter(filePath, number);
splitter.addIProgress(this); //订阅通知
splitter.addIProgress(&cn); // 订阅通知
splitter.split();
splitter.removeIProgress(this);
}
virtual void DoProgress(float value){
progressBar->setValue(value);
}
};
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
std::cout << ".";
}
};

使用面向对象的抽象,Observer模式使我们可以独立地改变目标与观察者,使二者之间的依赖关系松耦合

目标发送通知,无需指定观察者,通知可以自动传播,观察者自己决定是否订阅通知,目标对象对此一无所知。

Observer模式是基于事件的UI框架中非常常用的设计模式,是MVC模式中重要组成。


设计模式
http://example.com/2024/07/31/设计模式/
作者
John Doe
发布于
2024年7月31日
许可协议