观察者模式

观察者模式

1.何为观察者模式

​ 观察者模式定义了一种一对多(变化)的依赖关系,以便当一个对象发生变化时,所有依赖与它的对象都能接受到通知并做出相应更新。

2.观察者模式的意义和使用场景

​ 在软件构建中,我们可能需要对一些对象建立一种“通知依赖关系”,当一个对象(目标对象)的状态发生了改变,,所有依赖对象(观察者)都能收到通知。观察者模式解开了二者之间的耦合,使得我们可以独立地改变目标和观察者。这是依赖倒置原则的体现。

3.观察者模式的实现

​ 创建一个观察者基类,所有观察者继承于该基类,去实现对不同状态的观察。在目标对象中包含一个观察者集合,代表订阅当前对象的观察者,当目标变化发生时,无需指定某个观察者,通知(可以携带通知信息作为参数)将自动传播到所有订阅观察者。观察者自己决定是否订阅,目标对象对此一无所知。

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
// 抽象观察者类
class Observer {
public:
virtual void update() = 0;
};
// 具体观察者类 - 学生
class Student : public Observer {
private:
std::string name;

public:
Student(const std::string& name) : name(name) {}

void update() override {
std::cout << name << " received a notification" << std::endl;
}
};
// 抽象主题类
class Subject {
private:
std::vector<Observer*> observers;

public:
// 注册观察者
void attach(Observer* observer) {
observers.push_back(observer);
}

// 取消注册观察者
void detach(Observer* observer) {
for (auto it = observers.begin(); it != observers.end(); ++it) {
if (*it == observer) {
observers.erase(it);
break;
}
}
}

// 通知所有观察者
void notify() {
for (Observer* observer : observers) {
observer->update();
}
}
};
// 具体主题类 - 教师
class Teacher : public Subject {
public:
void teach() {
std::cout << "Teacher is teaching" << std::endl;
notify(); // 通知所有观察者
}
};

中介者模式

中介者模式

1.何为中介者模式

​ 使用一个中介对象来封装(封装变化)一系列的对象交互。它使得各个对象不需要显示地相互引用(编译时依赖–>运行时依赖),从而达到解耦的目的,而且可以独立地改变它们的交互

2.中介者模式的意义和使用场景

​ 在软件系统中,经常会出现一系列对象相互引用的情况,这种紧耦合非常不利于应对变化。中介者模式本质上提出引入中间组件,使将多个对象间的控制逻辑进行集中,把“多个对象相互关联”转为“多个对象和一个中介者关联”,实现了解耦。值得注意的是,外观模式是解耦系统间(单向)的对象关联关系,中介者模式是解耦系统内各个对象间(双向)的关联关系

3.中介者模式的实现

​ 中介者模式的实现多种多样,中介组件内部可能非常复杂。但究其本质都是使系统中的各个成员都引用该中间组件,并通过它来相互通信。当中介者过于复杂的 时候,考虑拆分成多个中介者。

命令模式

命令模式

1.何为命令模式

​ 将一个请求封装为一个对象,向对象发送命令来指导它们做事,从而可用不同的请求来指导对象不同的行为。如此可以实现请求排队、记录请求日志、撤销请求、事务等操作。实现“行为”和“组件”的解耦。

2.命令模式的意义和使用场景

​ 在软件构建过程中,行为请求者和行为实现者通常是紧耦合关系,但在某些场合需要对请求进行记录、撤销、事务等操作,这种耦合是不合适的。所以将操作指令封装为一个命令对象,用于对象之间的通信,比通过函数参数通信更方便、灵活。

3.命令模式的实现

​ 设计一个命令抽象基类,然后通过继承实现各种命令。也可以实现复合命令,即命令对象中用一个数组存放命令集。还可以将 查询命令和请求命令分开,就像sql一样。

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
// 命令基类
class Command {
public:
virtual void execute() = 0;//执行
virtual void undo() = 0;//撤销
};

// 命令接收者
class Light {
public:
void turnOn() {
std::cout << "Light is ON" << std::endl;
}

void turnOff() {
std::cout << "Light is OFF" << std::endl;
}
};
// 具体命令
class TurnOnLightCommand : public Command {
private:
Light& light;
string args;//命令参数
public:
TurnOnLightCommand(Light& l,arg) : light(l),args(arg) {}
void execute() override {
light.turnOn();
}
void undo() override {
light.turnOff();
}
};
// 具体命令
class TurnOffLightCommand : public Command {
private:
Light& light;
string args;//命令参数
public:
TurnOffLightCommand(Light& l) : light(l),args(arg) {}

void execute() override {
light.turnOff();
}
void undo() override {
light.turnOn();
}
};
//复合命令
class LightCommands : public Command {
private:
vector<Command*> commands;
public:
TurnOffLightCommand() {}
void addcommand(Command* cmd)
void execute() override {
for(auto i : commands)
{
i->execute();
}
}
};


需要指出的是,在c++中由于性能的需求,实际应用中,许多命令模式被范式编程+仿函数代替。命令模式用接口-实现来定义行为接口规范,更严格,但性能有所损失。函数对象(仿函数)以函数签名定义行为规范,更灵活,性能高。

代理模式

代理模式

1.何为代理模式

​ 为其他对象提供一种代理以控制(隔离、使用接口)对这个对象的访问。代理模式与装饰模式的区别是,它不会拓展对象的功能,它只是强化现有对象的潜在行为。

2.代理模式的意义和使用场景

​ 有些对象由于某种原因(如创建开销大、需要安全加密、需要远程访问等),直接访问会带来许多问题,甚至不能直接访问。这是我们使用一个代理类,来访问该对象。如在分布式系统中,要访问存在于服务器的对象,无法直接访问。必须通过代理类,在代理类中封装网络通信的细节,实现服务器对象的访问。

3.代理模式的实现

​ 代理模式的实现多种多样,核心是代理类的实现。代理类很多时候是框架自动生成的。有很多种代理类型,智能指针便是一种代理模式的实现。

享元模式

享元模式

1.何为享元模式

​ 运用共享技术有效地支持大量细粒度对象,本质上是一种节约内存空间的技术,具体实现多种多样。池化技术便是享元模式的应用。

2.享元模式的意义和使用场景

​ 享元模式通常用于具有大量非常相似的对象的场景。它使用对象共享的方法来避免创建大量的对象,从而节约内存。

3.享元模式的实现

​ 享元模式的实现多种多样,核心是对象共享。要注意对象状态的处理。下面是一种实现

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
// 抽象享元类
class Shape {
public:
virtual void draw() = 0;
};
// 具体享元类 - 圆形
class Circle : public Shape {
private:
int x, y, radius;
std::string color;
public:
void draw() override {
}
};
// 享元工厂类
class ShapeFactory {
private:
std::map<std::string, Shape*> shapes; // 存储已经创建的享元对象

public:
// 获取一个圆形享元对象
Shape* getCircle(const std::string& color) {
if (shapes.find(color) == shapes.end()) {
// 如果还没有指定颜色的圆形对象,则创建一个新的,并添加到已创建的对象中
shapes[color] = new Circle(0, 0, 10, color);
}
return shapes[color];
}
};

外观模式

外观(门面)模式

1.何为外观模式

​ 为子系统中的一组接口提供一个一致、稳定的界面(中间层),外观模式定义了一个高层的接口,将子系统的变化隔离。

2.外观模式的意义和使用场景

​ 在组件构建时,接口间直接依赖可能会导致很多问题,这时,提供一个稳定的中间接口。如此便可以隔离子系统的变化,无论子系统实现如何改变,外部调用者只需要和中间层的稳定的接口打交道。这达到了解耦的效果—子系统内部的任何变化不会影响到中间层接口。

3.外观模式的实现

​ 外观模式注重从架构的层次看待整个系统,而不是单个类的层次。它更多是一种架构设计模式。所以没有固定的代码实现,只需要封装一个中间层,隔离子系统和使用者。就像操作系统一样,隔离计算机使用者和计算机硬件,不论硬件如何变化,计算机的使用方式不变。

​ 值得注意的是,外观模式封装的子系统内部应该是相互耦合关系比较大的一组组件,而不是一个简单的功能集合。

装饰模式

装饰模式

1.何为装饰模式

​ 装饰模式动态地(组合)给一个对象增加一些额外的职责。就增加功能而言,它比继承更灵活。它能让我们在不修改原始类型也不产生大量派生类的情况下拓展功能(开闭原则)

2.装饰模式的意义和使用场景

​ 某些情况下我们可能会“过度使用继承来扩展对象的功能”,这缺乏灵活性,会导致子类急剧膨胀。装饰器模式将继承拓展改为组合拓展,在运行时动态地去拓展对象功能,符合了开闭原则,增加了灵活性。

3.装饰模式的实现

​ 创建一个装饰基类,装饰类继承于它。装饰类中有要装饰的对象的抽象基类(组合),以此来支持对同一基类不同子类对象的装饰。

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
74
75
76
77
78
79
80
81
82
83
// 基类流
class Stream {
public:
virtual void write(const std::string& data) = 0;
virtual std::string read() = 0;
};
// 具体流实现 - 网络流
class NetworkStream : public Stream {
public:
void write(const std::string& data) override {
}
std::string read() override {
}
};
// 具体流实现 - 文件流
class FileStream : public Stream {
public:
void write(const std::string& data) override {
}
std::string read() override {
}
};

// 具体流实现 - 内存流
class MemoryStream : public Stream {
public:
void write(const std::string& data) override {
}
std::string read() override {
}
};

// 装饰器基类
class StreamDecorator : public Stream { //这里继承是为了继承stream的接口
protected:
Stream* stream;
};

// 具体装饰器 - 加密
class EncryptionDecorator : public StreamDecorator {
public:
EncryptionDecorator(Stream* stream) : StreamDecorator(stream) {}

void write(const std::string& data) override {
// 在写入前对数据进行加密处理
stream->write(encryptedData);
}

std::string read() override {
// 在读取后对数据进行解密处理
return stream->read();
}
};

// 具体装饰器 - 缓存
class CachingDecorator : public StreamDecorator {
public:
CachingDecorator(Stream* stream) : StreamDecorator(stream) {}

void write(const std::string& data) override {
// 在写入前对数据进行缓存处理
stream->write(encryptedData);
}

std::string read() override {
// 在读取后对数据进行缓存处理
return stream->read();
}
};

int main() {
// 创建具体流对象
Stream* networkStream = new NetworkStream();
Stream* fileStream = new FileStream();
Stream* memoryStream = new MemoryStream();
// 使用装饰器进行加密和缓存
Stream* encryptedNetworkStream = new EncryptionDecorator(networkStream);
Stream* cachedFileStream = new CachingDecorator(fileStream);
// 写入和读取数据
encryptedNetworkStream->write("Data to encrypt and send over network");
cachedFileStream->write("Data to write in file and cache");

}

组合模式

组合模式

1.何为组合模式

​ 组合模式将对象组合成树形结构来表示“部分–整体”层次接口。它为单个对象和容器对象提供相同的接口,使得用户对于单个对象和对象集合的使用具有一致性(稳定)

2.组合模式的意义和使用场景

​ 将用户代码和复杂的容器结构解耦是组合模式的核心思想。解耦之后用户代码纯粹与抽象接口产生依赖,而非与容器内部的实现结构产生依赖,以此来应对变化。

3.组合模式的实现

​ 由抽象基类继承出父节点类和子节点类,在重写父节点的方法时,遍历子节点去调用子节点的相应方法。以此,用户代码就只需要调用父节点的方法,就可以实现对子节点方法的调用。

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
// 组件抽象类(Component)
class Component {
public:
virtual void operation() = 0;
virtual void add(Component* component) {}
virtual void remove(Component* component) {}
virtual Component* getChild(int index) { return nullptr; }
};

// 叶子组件类(Leaf)
class Leaf : public Component {
public:
void operation() override {
// 叶子节点的操作
std::cout << "Leaf operation" << std::endl;
}
};

// 容器组件类(Composite)
class Composite : public Component {
private:
std::list<Component*> children; //list中既可以是叶子节点也可以是普通节点

public:
void operation() override {
// 容器节点的操作
std::cout << "Composite operation" << std::endl;
// 遍历所有子节点执行操作
for (auto component : children) {
component->operation();
}
}
void add(Component* component) override {
children.push_back(component);
}
void remove(Component* component) override {
children.remove(component);
}
Component* getChild(int index) override {
}
};

int main() {
Component* root = new Composite();

Component* leaf1 = new Leaf();
Component* leaf2 = new Leaf();
Component* leaf3 = new Leaf();

root->add(leaf1);

Component* subComposite = new Composite();
subComposite->add(leaf2);
subComposite->add(leaf3);

root->add(subComposite);

root->operation();


return 0;
}

桥接模式

1.何为桥接模式

​ 桥接模式通常作为连接器或粘合剂,将两个“不相关的组件“连接起来。抽象的使用允许组件之间在不了解具体实现的情况下彼此交换(使用多态)。将抽象部分(业务功能)和实现部分(平台实现)分离,使它们可以独自变化

2.桥接模式的意义和使用场景

​ 有些类由于其实现逻辑,会有两个乃至多个维度的变化。这会导致类的数量急剧膨胀,耦合增强。如有n个业务类,m个平台实现类,那么就需要n*m个子类来实现这两个类的功能。桥接模式使用“对象间的组合关系”解耦了抽象和实现间的耦合。

3.桥接模式的实现

​ 在业务实现类里包含平台实现抽象基类,业务实现类继承自业务抽象基类。用不同平台对象去初始化业务实现对象就可以得到不同功能的实现。于是我们将n*m个功能子类转换成了n+m个子类。同时实现了业务功能和平台实现的独立变化(解耦)

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
// 实现部分的接口(Implementor)
class Implementor {
public:
virtual void operationImpl() = 0;
};

// 具体实现类A(Concrete Implementor A)
class ConcreteImplementorA : public Implementor {
public:
void operationImpl() override {
// 实现A的操作
}
};

// 具体实现类B(Concrete Implementor B)
class ConcreteImplementorB : public Implementor {
public:
void operationImpl() override {
// 实现B的操作
}
};

// 抽象部分的接口(Abstraction)
class Abstraction {
protected:
Implementor* implementor;

public:
Abstraction(Implementor* impl) : implementor(impl) {}

virtual void operation() = 0;
};

// 扩展抽象部分的类(Refined Abstraction)
class RefinedAbstraction : public Abstraction {
public:
RefinedAbstraction(Implementor* impl) : Abstraction(impl) {}

void operation() override {
// 执行其他操作
implementor->operationImpl();
// 执行其他操作
}
};

int main() {
Implementor* implementorA = new ConcreteImplementorA();
Abstraction* abstractionA = new RefinedAbstraction(implementorA);

abstractionA->operation();

Implementor* implementorB = new ConcreteImplementorB();
Abstraction* abstractionB = new RefinedAbstraction(implementorB);

abstractionB->operation();

delete abstractionA;
delete implementorA;

delete abstractionB;
delete implementorB;

return 0;
}

适配器模式

适配器模式

1.何为适配器模式

​ 将一个类的接口转换成另一种用户需要的接口,根据已有的接口来得到另一个不同的接口,来满足我们的需要

2.适配器模式的意义和使用场景

​ 在软件系统中,由于应用环境的变化,可能需要将现有的对象放到新的环境中使用。而现有对象的接口无法满足使用需求。这时我们需要使用适配器来完成新旧接口的转换。使得一个类的接口转换成另一种希望的接口,来满足我们的需要。

3.适配器模式的实现

​ 只需要定义一个适配器抽象基类,在定义好新的接口,再用已有的接口去实现子类中相应的新接口即可。

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
// 目标接口(Target)
class Target {
public:
virtual void request() = 0;
};

// 需要适配的类(Adaptee)
class Adaptee {
public:
void specificRequest() {
// 进行特殊的操作
}
};

// 适配器类(Adapter)
class Adapter : public Target {
private:
Adaptee* adaptee;//这里可能有很多需要适配的类

public:
Adapter(Adaptee* a) : adaptee(a) {}

void request() override {
adaptee->specificRequest();
// 进行其他适配操作
}
};