前言

介绍

设计模式:就是在解决某一类问题场景时,有既定的优秀的代码框架可以直接使用,有以下优点可取:

(1)代码更易于维护,代码的可读性、复用性、可移植性、健壮性更好。

(2)当软件原有需求有变更或者增加新的需求时,合理的设计模式的应用,能够做到软件设计要求的"开-闭原则",即对修改关闭,对扩展开放,使软件原有功能修改,新功能扩充非常灵活。

(3)合理的设计模式的选择,会使软件设计更加模块化,积极的做到软件设计遵循的“高内聚,低耦合”这一根本原则。

设计模式的7大基本原则

设计原则 精简记忆 定义和目的
单一职责原则 一件事:一个类只应做一件事。 一个类只负责一个职责,避免类的功能过于复杂,提升可读性和维护性。
开闭原则 扩展开放,修改关闭:新增功能不改代码。 对扩展开放,对修改关闭,通过扩展类的方式实现功能的增强,而避免修改已有代码。
里氏替换原则 子类能替父类:子类替换父类不出问题。[子类应该扩展父类的行为,而不是破坏父类的行为。] 子类必须能够替代父类而不影响程序的正确性,保证继承的合理性。
依赖倒置原则 依赖抽象:依赖接口,不依赖具体实现。 高层模块不应该依赖低层模块,二者都应该依赖于抽象,降低模块之间的耦合。
接口隔离原则 小接口:多个小接口,避免大而全接口。 使用多个小接口,而不是一个大接口,避免让实现类依赖不需要的接口方法。
迪米特法则 少知道:少与其他对象打交道。 一个对象应该对其他对象有尽可能少的了解,减少对象之间的耦合,提升系统灵活性。
合成复用原则 优先组合:用组合代替继承来复用代码。 尽量使用组合而不是继承来实现代码复用,避免继承带来的强耦合问题。

解释:

里氏替换原则(Liskov Substitution Principle,LSP)

  • 定义:所有引用基类的地方必须能够透明地使用其子类的对象。
  • 解释:子类对象应该能够替换父类对象而不影响程序的正确性。也就是说,子类在继承父类时,不能破坏父类原有的功能和行为。
  • 好处:确保子类能够完善地替代父类,增强系统的灵活性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

// 基类 Bird
class Bird {
public:
// 使用虚函数,允许子类覆盖
virtual void fly() {
std::cout << "Bird is flying" << std::endl;
}
};

// 子类 Ostrich
class Ostrich : public Bird {
public:
// 覆盖 fly 方法
void fly() override {
std::cout << "Ostrich cannot fly" << std::endl;
}
};

依赖倒置原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

class LaserPrinter {
public:
void print() {
std::cout << "Printing using a Laser Printer." << std::endl;
}
};

class Printer {
public:
void printDocument() {
LaserPrinter laserPrinter;
laserPrinter.print();
}
};

int main() {
Printer printer;
printer.printDocument();
return 0;
}

在这个例子中,Printer 类直接依赖于 LaserPrinter 类。如果我们以后想更换为其他类型的打印机(例如喷墨打印机),我们就必须修改 Printer 类,这违反了依赖倒置原则。

单例模式

单例模式顾名思义,保证一个类仅可以有一个实例化对象,并且提供一个可以访问它的全局接口。这是一个创建性模式(主要是指对象的创建方式)。单例模式和多线程结合到是很紧密的。包括两种单例模式,以及线程安全的问题

应用

第一种:日志模块

假如 这里有一个类,提供了很多方法来封装了一个日志模块。一个软件本身可能有很多的功能模块,那么这么多的模块都要通过日志模块进行写入一些软件运行的日志信息到磁盘内。应该是把这些所有的日志信息都给到一个日志模块的对象上。

第二种:数据库模块

作为一个个的数据库客户端,是要通过数据库模块与数据库服务器进行交互通信的。那么这个数据库client就可以设计成一个单例:应用软件的其他功能模块如果要与数据库服务器进行交互通信,可以调用一个数据库模块对象的某一个方法即可,不需要去创建那么多的对象。毕竟数据库的请求那么多,不可能做到 每一次处理请求都生成一个数据库对象,这样会导致数据库模块对象特别特别多和数据库的连接也特别的多,占用大量内存且非常麻烦。

实现单例模式必须注意一下几点:

(1)单例类只能有一个实例化对象。

(2)单例类必须自己提供一个实例化对象。

(3)单例类必须提供一个可以访问唯一实例化对象的接口。

(4)单例模式分为懒汉和饿汉两种实现方式。

单例:

饿汉式 对象在程序执行时优先创建【线程安全】

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
#include <stdio.h>
#include <iostream>

using namespace std;

class Singleton
{
public:
static Singleton *getInstance()
{
return &instance;
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;

private:
static Singleton instance;
Singleton() {}
};
Singleton Singleton::instance;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
cout << p1 << " " << p2 << endl;
// Singleton p = *p1;
// cout << &p << endl;
return 0;
}
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
#include <stdio.h>
#include <iostream>

using namespace std;

class Singleton
{
public:
static Singleton *getInstance()
{
return &instance;
}
//Singleton(const Singleton &) = delete;
//Singleton &operator=(const Singleton &) = delete;

private:
static Singleton instance;
Singleton() {}
};
Singleton Singleton::instance;

int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
cout << p1 << " " << p2 << endl;

// 允许拷贝构造,会产生一个新的 Singleton 实例
Singleton p = *p1;
cout << &p << endl;

return 0;
}

懒汉式: 对象的创建在第一次调用getInstance函数时创建【线程不安全】

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
#include <stdio.h>
#include <iostream>

using namespace std;

class Singleton
{
public:
static Singleton *getInstance()
{
if (instance == nullptr)
{
instance = new Singleton();
}
return instance;
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;

private:
static Singleton *instance;
Singleton() {}
};
Singleton *Singleton::instance = nullptr;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
cout << p1 << " " << p2 << endl;
// Singleton p = *p1;
// cout << &p << endl;
return 0;
}

上述懒汉式如何改成线程安全的

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
#include <stdio.h>
#include <iostream>
#include <mutex>
using namespace std;

class Singleton
{
public:
static Singleton *getInstance()
{
if (instance == nullptr)
{
// 锁加双重判断
unique_lock<mutex>(mtx);
if (instance == nullptr)
{
instance = new Singleton();
}
}
return instance;
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;

private:
static Singleton *instance;
mutex mtx;
Singleton() {}
};
Singleton *Singleton::instance = nullptr;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
cout << p1 << " " << p2 << endl;
// Singleton p = *p1;
// cout << &p << endl;
return 0;
}

单例模式缺点

  1. 全局状态
    • 缺点:单例模式会在应用程序中引入全局状态,这可能导致代码的耦合度增加,使得代码更难以测试和维护。
    • 举例:假设你有一个 Logger 单例类,它在整个应用程序中记录日志。如果其他类直接依赖于这个单例类,那么在单元测试中模拟或隔离这个依赖就会变得困难。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Logger {
private:
Logger() {}
static Logger* instance;
public:
static Logger* getInstance() {
if (instance == nullptr) {
instance = new Logger();
}
return instance;
}
void log(const std::string& message) {
std::cout << message << std::endl;
}
};

Logger* Logger::instance = nullptr;

void someFunction() {
Logger::getInstance()->log("Logging from someFunction");
}

在这个例子中,Logger 类是一个单例类。someFunction 函数直接依赖于 Logger 单例类,这使得在单元测试中难以隔离 Logger 类。

  1. 难以扩展
    • 缺点:由于单例模式限制了类的实例数量,因此很难扩展或修改类的行为。
    • 举例:假设你有一个 Configuration 单例类,它存储应用程序的配置信息。如果将来需要支持多个配置实例,那么修改代码将会非常困难。
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
class Configuration {
private:
Configuration() {}
static Configuration* instance;
public:
static Configuration* getInstance() {
if (instance == nullptr) {
instance = new Configuration();
}
return instance;
}
void setConfig(const std::string& key, const std::string& value) {
// 设置配置
}
std::string getConfig(const std::string& key) {
// 获取配置
return "some_config_value";
}
};

Configuration* Configuration::instance = nullptr;

void someFunction() {
Configuration::getInstance()->setConfig("key", "value");
std::string value = Configuration::getInstance()->getConfig("key");
std::cout << value << std::endl;
}

在这个例子中,Configuration 类是一个单例类。如果将来需要支持多个配置实例,那么修改代码将会非常困难。

  1. 线程安全问题
    • 缺点:在多线程环境中,单例模式的实现可能会导致线程安全问题。
    • 举例:在多线程环境中,如果没有适当的同步机制,多个线程可能会同时创建单例类的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton {
private:
Singleton() {}
static Singleton* instance;
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};

Singleton* Singleton::instance = nullptr;

在这个例子中,Singleton 类的 getInstance 方法不是线程安全的。在多线程环境中,多个线程可能会同时创建 Singleton 类的实例。

为了解决这个问题,可以使用双重检查锁定模式(Double-Checked Locking Pattern)或其他同步机制来确保线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <mutex>

class Singleton {
private:
Singleton() {}
static Singleton* instance;
static std::mutex mtx;
public:
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

在这个例子中,使用 std::mutex 来确保 getInstance 方法是线程安全的。

简单工厂

简单工厂:它不属于标准的OOP设计模式中,而后面两种是包含在标准的OOP的23种设计模式中的。

为什么要工厂模式:主要是封装了对象的创建过程。 创建性模式本身就是体现了:对象的创建过程的封装和隐藏。没有工厂模式的封装就是:对象的new 和 new等。当代码里面出现很多的类,每次创建在对象的时候,都需要通过new 类名称的方式来生成对象。

先来看个代码

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
#include <iostream>
#include <string>

class Car
{
public :
//在构造函数的初始化列表里面给name初始化
Car(std::string name):_name(name){}

//提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0;//纯虚函数
protected:
std::string _name;
};

class BMW:public Car
{
public:
BMW (std::string name):Car(name){}
//重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi :public Car
{
public:
Audi(std::string name) :Car(name) {}
//重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};

int main()
{
Car* p1 = new BMW("x1");
Car* p2 = new Audi("A6");
p1->show();
p2->show();
delete p1;
delete p2;
/*
上面的缺点在于:
1 需要记住派生类的名字,不然没法new对象
2 创建对象的时候,直接使用了对象

其实我们不需要去了解对象创建的具体内容
只要给我汽车就OK了,劳资不需要去知道怎么造车的
*/
return 0;
}

使用简单工厂

解决办法为:把这些对象都给 封装到一个简单的工厂里面。如下:

工厂方法

通过下面这个简单的工厂把所有对象的创建给封装起来了,下面造汽车劳资不用管,我只是想要一辆车,通过传入不同的参数,得到不同的对象。

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
#include <iostream>
#include <string>

class Car
{
public:
// 在构造函数的初始化列表里面给name初始化
Car(std::string name) : _name(name) {}

// 提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0; // 纯虚函数
protected:
std::string _name;
};

class Bmw : public Car
{
public:
Bmw(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi : public Car
{
public:
Audi(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};
enum CarType
{
BMW,
AUDI
};
class SimpleFactory
{
public:
Car *createCar(CarType type)
{
switch (type)
{
case BMW:
return new Bmw("x1");
case AUDI:
return new Audi("A6");
default:
break;
}
return nullptr;
}
};
int main()
{
SimpleFactory *factory = new SimpleFactory();
Car *car1 = factory->createCar(BMW);
Car *car2 = factory->createCar(AUDI);
car1->show();
car2->show();
delete factory;
delete car1;
delete car2;
return 0;
}
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
#include <stdio.h>
#include <iostream>
#include <mutex>
#include <string>
using namespace std;

class F{
public:
F(string name):name_(name){}
virtual void show() =0;
string name_;
};

class A:public F{
public:
A(string name):F(name){}
void show(){
cout<<"AA:"<<name_;
}
};
class B:public F{
public:
B(string name):F(name){}
void show(){
cout<<"BB:"<<name_;
}
};

enum Type{
AA,BB
};
class SimpleFactory{
public:
F * create(Type type){
switch (type)
{
case AA:
return new A("aaa");
case BB:
return new B("bbb");
default:
break;
}
return nullptr;
}
};
signed main(){
SimpleFactory simpleFactory ;
simpleFactory.create(AA)->show();
return 0;
}

使用智能指针管理对象

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
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Car
{
public:
// 在构造函数的初始化列表里面给name初始化
Car(std::string name) : _name(name) {}

// 提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0; // 纯虚函数
protected:
std::string _name;
};

class Bmw : public Car
{
public:
Bmw(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi : public Car
{
public:
Audi(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};
enum CarType
{
BMW,
AUDI
};
class SimpleFactory
{
public:
Car *createCar(CarType type)
{
switch (type)
{
case BMW:
return new Bmw("x1");
case AUDI:
return new Audi("A6");
default:
break;
}
return nullptr;
}
};
int main()
{
std::unique_ptr<SimpleFactory> factory(new SimpleFactory());

// SimpleFactory *factory = new SimpleFactory();
std::unique_ptr<Car> car1(factory->createCar(BMW));
std::unique_ptr<Car> car2(factory->createCar(AUDI));
car1->show();
car2->show();

return 0;
}

简单工厂的缺点:

同一个工厂既建宝马,也整奥迪。不可能用一个工厂把所有对象的创建都封装起来。而且工厂里面的设计也不符合 开闭原则

开闭原则

开闭原则是面向对象设计的一种重要原则,它的全称是“对扩展开放,对修改关闭”。这个原则的核心思想是,当应用的需求改变时,我们应该尽量通过添加新的代码进行扩展,而不是去修改已有的代码。

具体来说,开闭原则包含两个方面:

  • 对扩展开放:意味着我们应该设计出可以容易添加新功能的系统,只需要添加新的代码,而不需要修改原有的代码。
  • 对修改关闭:意味着一旦我们完成了系统的设计和编码,应该尽量避免对已有的代码进行修改。因为修改已有的代码会带来很多风险,可能会引入新的错误。

遵守开闭原则的好处是,可以使得系统更加稳定,更具有弹性,更容易进行维护和扩展。但同时,实现开闭原则也需要一定的设计和编码技巧,可能会增加系统的设计和实现的复杂性。

因此,需要根据上述根据对修改关闭,对扩展开放的原则进行修改。

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
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Car
{
public:
// 在构造函数的初始化列表里面给name初始化
Car(std::string name) : _name(name) {}

// 提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0; // 纯虚函数
protected:
std::string _name;
};

class Bmw : public Car
{
public:
Bmw(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi : public Car
{
public:
Audi(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};
enum CarType
{
BMW,
AUDI
};
class SimpleFactory
{
public:
virtual Car *createCar(string name) = 0;
};
class BmwFactoryMethod : public SimpleFactory
{

Car *createCar(string name)
{
return new Bmw(name);
}
};
class AUDIFactoryMethod : public SimpleFactory
{

Car *createCar(string name)
{
return new Audi(name);
}
};
int main()
{
std::unique_ptr<SimpleFactory> Afactory(new AUDIFactoryMethod());
std::unique_ptr<SimpleFactory> Bfactory(new BmwFactoryMethod());
// SimpleFactory *factory = new SimpleFactory();
std::unique_ptr<Car> car1(Afactory->createCar("x1"));
std::unique_ptr<Car> car2(Bfactory->createCar("A5"));
car1->show();
car2->show();

return 0;
}

一种写法

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
#include <stdio.h>
#include <iostream>
#include <mutex>
#include <string>
#include <memory>
using namespace std;
class F{
public:
F(string name):name_(name){}
virtual void show() =0;
string name_;
};
class A:public F{
public:
A(string name):F(name){}
void show(){
cout<<"AA:"<<name_;
}
};
class B:public F{
public:
B(string name):F(name){}
void show(){
cout<<"BB:"<<name_;
}
};
class SimpleFactory{
public:
virtual F* create(string name)=0;
};
class AFun:public SimpleFactory{
public:
F* create(string name){
return new A(name);
}
};
signed main(){
unique_ptr<SimpleFactory> nna(new AFun());
unique_ptr<F> aaaa(nna->create("aa"));
// unique_ptr<A> aaaa(new A("aa"));
aaaa->show();
return 0;
}

把工厂划分为一个继承结构:封装了一个工厂方法(纯虚函数)。派生类代表具体的工厂,产生具体的产品。(相应的工厂创建相应的产品),达到了一个工厂其相应的产品。

这就是对已有的功能进行封闭。开闭原则:对修改关闭,对扩展开放 此时要是删除一种产品的工厂,直接删除相应的派生类即可(也不会改动其他的类)。在调用的时候,想要获取一个对象,只需要调用 工厂相应的工厂方法就可以了,不需要去了解派生类叫什么名字、也不需要知道对象是怎么创建的(这些细节由工厂进行维护)。

抽象工厂

简单工厂的缺点如下:

比如说是 一类有关联关系的产品:手机、手机上的耳机等。根据上面的工厂方法,创建耳机,也得整上一个相应的耳机工厂,这样做 就有些不太现实(具体的一个个工厂类就太多了)。也就是这些一系列有关联关系的产品 应该放在一个工厂进行创建的。 毕竟有关联关系的产品太多了,不可能各个产品都创建相对应的工厂(工厂类将会极为庞大)。

解决方法:将工厂方法升级为抽象工厂。抽象工厂也是需要工厂方法的,抽象工厂:对一系列有关联关系的产品簇提供产品对象的统一创建。

就可以把一组有关联关系的产品簇提供产品对象的统一创建 放到一个工厂里面做了。

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <iostream>
#include <string>
#include <memory>

// 一系列产品1 汽车
class Car
{
public:
// 在构造函数的初始化列表里面给name初始化
Car(std::string name) : _name(name) {}

// 提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0; // 纯虚函数
protected:
std::string _name;
};
class Bmw : public Car
{
public:
Bmw(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi : public Car
{
public:
Audi(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};
// 一系列产品2 车灯
class CarLight
{
public:
virtual void light() = 0;
};
class BmwLight : public CarLight
{
public:
// 重写从基类基类继承过来的纯虚函数
void light()
{
std::cout << "宝马车灯 " << std::endl;
}
};
class AudiLight : public CarLight
{
public:
// 重写从基类基类继承过来的纯虚函数
void light()
{
std::cout << "奥迪车灯 " << std::endl;
}
};
// 工厂方法 升级为 抽象工厂
// 抽象工厂:对有一组关联关系的产品簇提供产品对象的统一创建
class AbstractFactory
{
public:
// 下面是工厂方法,提供多个产品创建的抽象接口
// 车子产品方法 创建车子
virtual Car *createOneCar(std::string) = 0;
// 车灯产品方法 创建车灯
virtual CarLight *createCarLight() = 0;
};
// 下面是宝马工厂,负责创建宝马系列的产品
class BMWAbstractFactory : public AbstractFactory
{
Car *createOneCar(std::string name) // 创建车子
{
return new Bmw(name);
}
CarLight *createCarLight() // 创建车灯
{
return new BmwLight();
}
};
// 下面是奥迪工厂,负责创建奥迪系列的产品
class AUDIAbstractFactory : public AbstractFactory
{
Car *createOneCar(std::string name) // 创建车子
{
return new Audi(name);
}
CarLight *createCarLight() // 创建车灯
{
return new AudiLight();
}
};

/*
这里相较于简单工厂:使用一个工厂,通过传入不同的标识 来创建不同的对象
相当于给了一个工厂的基类,然后通过实现具体的产品的工厂。用这个具体产品的工厂
来创建具体的产品对象。

*/
int main()
{
std::unique_ptr<AbstractFactory> bmwAbstractFactory(new BMWAbstractFactory());
std::unique_ptr<AbstractFactory> audiAbstractFactory(new AUDIAbstractFactory());
std::unique_ptr<Car> p1(bmwAbstractFactory->createOneCar("x1"));
std::unique_ptr<Car> p2(audiAbstractFactory->createOneCar("A5"));

std::unique_ptr<CarLight> p3(bmwAbstractFactory->createCarLight());
std::unique_ptr<CarLight> p4(audiAbstractFactory->createCarLight());
p1->show();
p2->show();

p3->light();
p4->light();
return 0;
}

代理模式

结构型模式:(不是关注于对象的产生,关注于最后通过类与类的组合之后,功能上该怎么使用?以及对问题场景的符合与否?)

这些设计模式关注于类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

代理模式:Proxy Pattern 。主要体现的是 对象访问权限的控制。这个Proxy代理可以把那些访问对象的权限不够的用户 都给挡回去。

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <iostream>
#include <string>
#include <memory>

// 代理模式
class VedioWebSite // 抽象类
{
public:
virtual void freeMovie() = 0; // 免费电影
virtual void vipMovie() = 0; // VIP电影
virtual void ticketMovie() = 0; // VIP + 券电影
};

// 实际的 操作视频服务器后台的所有电影
class OperatorMovieBoss : public VedioWebSite // 这个是 委托类
{
public:
virtual void freeMovie() // 免费电影
{
std::cout << "你可以看免费电影" << std::endl;
}
virtual void vipMovie() // VIP电影
{
std::cout << "你可以看VIP电影" << std::endl;
}
virtual void ticketMovie() // VIP + 券电影
{
std::cout << "你可以看VIP + 券电影" << std::endl;
}
};
// 代理OperatorMovieBoss的代理类
class FreeMovieProxy : public VedioWebSite // 免费电影代理类
{
public:
FreeMovieProxy()
{
// 指针指向代理的对象
pvedio = new OperatorMovieBoss();
}
~FreeMovieProxy()
{
delete pvedio;
}
virtual void freeMovie()
{
// 通过代理对象的freeMovie来访问真正委托类对象的freeMovie方法
pvedio->freeMovie();
}
virtual void vipMovie() // VIP电影
{
std::cout << "你只是普通用户,不可以看VIP电影,请升级为会员"
<< std::endl;
}
virtual void ticketMovie() // VIP + 券电影
{
std::cout << "你没有电源券,不可以观看电影,请先升级为会员然后购买影券"
<< std::endl;
}

private:
// 这里需要一个组合的对象
/*
基类指针指向派生类对象,只是这里不直接指向委托类对象
而是直接访问代理对象,用代理对象来控制用户对委托类对象
访问权限的问题。既然也是从抽象类继承来的,3个纯虚函数都要去
重写一下。不然这个代理类也成了抽象类
*/
VedioWebSite *pvedio;
};
// 代理OperatorMovieBoss的代理类
class VIPMovieProxy : public VedioWebSite // VIP电影代理类
{
public:
VIPMovieProxy()
{
// 指针指向代理的对象
pvedio = new OperatorMovieBoss();
}
~VIPMovieProxy()
{
delete pvedio;
}
virtual void freeMovie()
{
// 通过代理对象的freeMovie来访问真正委托类对象的freeMovie方法
pvedio->freeMovie();
}
virtual void vipMovie() // VIP电影
{
pvedio->vipMovie();
}
virtual void ticketMovie() // VIP + 券电影
{
std::cout << "你没有电源券,不可以观看电影,请先购买影券" << std::endl;
}

private:
// 这里需要一个组合的对象
/*
基类指针指向派生类对象,只是这里不直接指向委托类对象
而是直接访问代理对象,用代理对象来控制用户对委托类对象
访问权限的问题。既然也是从抽象类继承来的,3个纯虚函数都要去
重写一下。不然这个代理类也成了抽象类
*/
VedioWebSite *pvedio;
};
void WhoWatchMovie(std::unique_ptr<VedioWebSite> &ptr)
{
ptr->freeMovie();
ptr->vipMovie();
ptr->ticketMovie();
}
int main()
{
// 基类指针 指向 普通用户级别的代理类
std::unique_ptr<VedioWebSite> p1(new FreeMovieProxy());
WhoWatchMovie(p1);
std::cout << "*****************************" << std::endl;
// 基类指针 指向 VIP用户级别的代理类
std::unique_ptr<VedioWebSite> p2(new VIPMovieProxy());
WhoWatchMovie(p2);

return 0;
}

总结:代理模式涉及到了 抽象类(公共类),委托类(需要从抽象类继承而来),代理类(需要从抽象类继承而来),以组合的方式 使用代理对象,在实际的使用中客户直接访问的是代理对象。

应用场景

远程代理

Spring Cloud Feign 使用远程代理机制。

举例说明:

我们假设有两个微服务:User ServiceOrder Service。其中,Order Service 需要调用 User Service 来获取用户信息。

1. 定义远程服务接口 (Feign Client)

首先,在 Order Service 中定义一个接口用于访问 User Service。这个接口就是通过 Feign 来实现的远程代理。

1
2
3
4
5
6
@FeignClient(name = "user-service") // 指定要调用的微服务名称
public interface UserClient {

@GetMapping("/users/{id}") // 映射远程服务的接口
User getUserById(@PathVariable("id") Long id);
}
  • @FeignClient:用于声明这是一个 Feign 客户端,name 表示要调用的服务名称,在服务注册中心(如 Eureka)中,user-service 是服务的名字。
  • @GetMapping:定义了远程服务的 HTTP 端点,这里表示调用 User Service/users/{id} 接口。

2. 在本地使用 Feign 客户端

Order Service 中,我们可以通过自动注入 UserClient 来调用远程的 User Service,就像调用本地方法一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/orders")
public class OrderController {

@Autowired
private UserClient userClient; // 注入 Feign 客户端

@GetMapping("/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
// 假设我们需要根据订单中的用户 ID 获取用户信息
User user = userClient.getUserById(1L); // 调用远程的 User Service
// 创建订单并返回
Order order = new Order(orderId, "Product A", 1L, user);
return order;
}
}

在这段代码中,userClient.getUserById(1L) 看起来像是调用了一个普通的本地方法,实际上它是通过 Feign 动态代理调用了远程的 User Service 服务的 /users/{id} 端点。

3. Feign 的工作原理

  • 动态代理:当 userClient.getUserById(1L) 被调用时,Feign 会生成一个代理对象来拦截这个方法调用,代理对象会将这个方法调用转化为一个HTTP 请求,向 User Service 发送请求。
  • 请求封装:Feign 根据 @GetMapping("/users/{id}") 注解,知道要发送一个 GET 请求到 /users/1,并将响应映射为 User 对象。
  • 响应处理User Service 返回的 HTTP 响应会被 Feign 解析,并自动转换为 User 对象返回给调用方。

动态代理

这里大致说下AOP-动态代理的实现

JDK 动态代理实现 Spring AOP 的底层原理

JDK 动态代理的原理

JDK 动态代理要求目标类必须实现一个或多个接口。通过 Proxy 类生成代理对象,代理对象实现了目标类的所有接口,并在代理对象内部,通过 InvocationHandler 接口实现对方法调用的拦截。

1.定义业务接口和实现类

假设我们有一个简单的业务接口 UserService,其中包含 addUser 方法。

1
2
3
4
5
6
7
8
9
10
11
12
// 业务接口
public interface UserService {
void addUser(String name);
}

// 业务实现类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("Adding user: " + name);
}
}

2.使用 JDK 动态代理实现 AOP 功能

我们通过实现 InvocationHandler 接口来拦截对 UserService 的方法调用,并在方法执行前后插入切面逻辑(比如日志记录)。

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 自定义的 InvocationHandler,用于拦截方法调用
public class AOPInvocationHandler implements InvocationHandler {

// 被代理的目标对象
private Object target;

// 构造方法,传入目标对象
public AOPInvocationHandler(Object target) {
this.target = target;
}

// 代理对象的方法调用会被转发到这个 invoke 方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在方法执行前插入的逻辑(前置通知)
System.out.println("Before method: " + method.getName());

// 执行目标方法
Object result = method.invoke(target, args);

// 在方法执行后插入的逻辑(后置通知)
System.out.println("After method: " + method.getName());

return result;
}

// 创建代理对象的方法
public static Object createProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口
new AOPInvocationHandler(target) // InvocationHandler 实现
);
}
}

3.测试动态代理的 AOP 功能

我们通过 AOPInvocationHandlerUserService 创建代理对象,并通过代理对象调用方法,观察 AOP 的效果。

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();

// 创建代理对象
UserService proxy = (UserService) AOPInvocationHandler.createProxy(userService);

// 调用代理对象的方法
proxy.addUser("John Doe");
}
}

日志代理

日志代理是一种常见的代理模式应用场景。代理模式的目的是在不修改原有对象的基础上,通过代理对象来控制对真实对象的访问或在访问之前和之后添加额外的功能。在日志代理中,代理对象会记录对原始对象的调用日志,从而方便对操作进行记录和管理。

场景:

假设我们有一个简单的银行账户类 BankAccount,它有存款和取款功能。现在我们希望在每次存款和取款时记录操作日志,便于调试和审计。我们可以通过代理模式来实现日志记录功能,而不需要修改 BankAccount 类的代码。

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
#include <iostream>
#include <string>

// 接口:银行账户(Subject)
class IBankAccount {
public:
virtual void Deposit(int amount) = 0;
virtual void Withdraw(int amount) = 0;
virtual ~IBankAccount() = default;
};

// 真实的银行账户类(Real Subject)
class BankAccount : public IBankAccount {
private:
int balance;
public:
BankAccount() : balance(0) {}

void Deposit(int amount) override {
balance += amount;
std::cout << "Deposited " << amount << ", current balance: " << balance << std::endl;
}

void Withdraw(int amount) override {
if (balance >= amount) {
balance -= amount;
std::cout << "Withdrew " << amount << ", current balance: " << balance << std::endl;
} else {
std::cout << "Insufficient balance. Current balance: " << balance << std::endl;
}
}
};

// 日志代理类(Proxy)
class BankAccountProxy : public IBankAccount {
private:
IBankAccount* realAccount; // 持有真实对象的引用
public:
BankAccountProxy(IBankAccount* account) : realAccount(account) {}

void Deposit(int amount) override {
std::cout << "[LOG] Deposit request: " << amount << std::endl;
realAccount->Deposit(amount); // 调用真实对象的方法
}

void Withdraw(int amount) override {
std::cout << "[LOG] Withdraw request: " << amount << std::endl;
realAccount->Withdraw(amount); // 调用真实对象的方法
}
};

int main() {
// 创建真实的银行账户对象
BankAccount account;

// 创建代理对象
BankAccountProxy proxy(&account);

// 通过代理对象操作账户,并记录日志
proxy.Deposit(100);
proxy.Withdraw(50);
proxy.Withdraw(100);

return 0;
}

输出:

1
2
3
4
5
6
[LOG] Deposit request: 100
Deposited 100, current balance: 100
[LOG] Withdraw request: 50
Withdrew 50, current balance: 50
[LOG] Withdraw request: 100
Insufficient balance. Current balance: 50

缓存代理

缓存代理是一种设计模式,主要用于避免重复计算或频繁访问资源带来的性能开销。通过缓存之前计算或访问的结果,下次请求相同数据时,可以直接从缓存中获取,避免再次计算或访问实际资源。

缓存代理的工作原理

  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
import java.util.HashMap;
import java.util.Map;

public class CacheProxy {
// 缓存存储计算结果
private Map<Integer, Integer> cache = new HashMap<>();

// 实际的计算过程
public int expensiveOperation(int input) {
// 检查缓存中是否有结果
if (cache.containsKey(input)) {
System.out.println("从缓存中获取结果...");
return cache.get(input);
}

// 如果缓存中没有,进行实际计算
System.out.println("执行复杂的计算...");
int result = input * input; // 模拟复杂计算

// 将结果存入缓存
cache.put(input, result);

return result;
}

public static void main(String[] args) {
CacheProxy proxy = new CacheProxy();

// 第一次调用,缓存中没有,执行计算
System.out.println("结果: " + proxy.expensiveOperation(4));

// 第二次调用相同参数,直接从缓存中获取
System.out.println("结果: " + proxy.expensiveOperation(4));

// 对不同参数进行调用,缓存中没有,执行计算
System.out.println("结果: " + proxy.expensiveOperation(5));
}
}

装饰器模式

结构型模式的一种:Decorator Pattern

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <iostream>
#include <string>
#include <memory>

using namespace std;
// 装饰器模式
class Car // 抽象基类
{
public:
virtual void show() = 0;
};
// 下面是三个 汽车实体类
class Bmw : public Car
{
public:
void show()
{
cout << "这是宝马汽车的配置为:";
}
};
class Audi : public Car
{
public:
void show()
{
cout << "这是奥迪汽车的配置为:";
}
};
class Bnze : public Car
{
public:
void show()
{
cout << "这是奔驰汽车的配置为:";
}
};
/*进行功能增强了,如下:*/
class CarBaseDecorator : public Car // 装饰器的基类
{
public:
// 装饰器里面,先把基本的功能装饰写好,给谁添加 先不重要

// 装饰器的基类,具体的功能装饰还没有出来呢
virtual void show() = 0;
};

/*
其实对于装饰器基类,如果要有一些装饰器公共的方法的话,
可以在装饰器的基类里面进行实现。
这里各个装饰器类没有一些公有的方法需要在统一地在基类书写的
情况下。

例如这里 就没有公共的方法,这种情况下就可以把装饰器的
基类CarBaseDecorator给省略掉。直接让下面的CarChildDecorator们
直接继承于Car类。

但是我选择不省略
*/

// 功能装饰器1:定速巡航功能
class CarChildDecorator1 : public CarBaseDecorator
{
public:
CarChildDecorator1(Car *p)
{
_ptr = p;
}
void show()
{
_ptr->show();
cout << " 定速巡航功能";
}

private:
Car *_ptr;
};

// 功能装饰器2:自动驾驶功能
class CarChildDecorator2 : public CarBaseDecorator
{
public:
CarChildDecorator2(Car *p)
{
_ptr = p;
}
void show()
{
_ptr->show();
cout << " 自动驾驶功能";
}

private:
Car *_ptr;
};
// 功能装饰器3:车道偏离功能
class CarChildDecorator3 : public CarBaseDecorator
{
public:
CarChildDecorator3(Car *p)
{
_ptr = p;
}
void show()
{
_ptr->show();
cout << " 车道偏离功能";
}

private:
Car *_ptr;
};
int main()
{
Car *p1 = new CarChildDecorator1(new Bmw());
p1 = new CarChildDecorator2(p1);
p1 = new CarChildDecorator3(p1);
p1->show();
cout << endl;
cout << "************************************" << endl;
Car *p2 = new CarChildDecorator2(new Audi());
p2->show();
cout << endl;
cout << "************************************" << endl;
Car *p3 = new CarChildDecorator3(new Bnze());
p3->show();
cout << endl;
return 0;
}

从深入理解对象模型的角度看有点套娃的味了,应用了虚函数指针的运行时多态实现。

上述代码详细解释:

在这个代码中,我们使用了装饰器模式,也称为包装器模式。装饰器模式允许我们在运行时动态地添加行为或状态到现有的对象中,而不需要修改其原始类的源代码。

当我们执行 p1->show(); 时,输出的结果是 “这是宝马汽车的配置为: 定速巡航功能 自动驾驶功能 车道偏离功能”,这是因为我们在创建 p1 时,逐步地向它添加了这些功能:

  1. Car *p1 = new CarChildDecorator1(new Bmw()); 在这一步,我们创建了一个 Bmw 对象,并将其作为参数传递给 CarChildDecorator1 的构造函数。这个操作把 “定速巡航功能” 添加到了 Bmw 对象中。
  2. p1 = new CarChildDecorator2(p1); 在这一步,我们创建了一个 CarChildDecorator2 对象,并将已经包含了 “定速巡航功能” 的 p1 作为参数传递给 CarChildDecorator2 的构造函数。这个操作把 “自动驾驶功能” 添加到了 p1 中。
  3. p1 = new CarChildDecorator3(p1); 在这一步,我们创建了一个 CarChildDecorator3 对象,并将已经包含了 “定速巡航功能” 和 “自动驾驶功能” 的 p1 作为参数传递给 CarChildDecorator3 的构造函数。这个操作把 “车道偏离功能” 添加到了 p1 中。

因此,当我们调用 p1->show(); 时,p1 中的每个装饰器都会调用其内部存储的 Car 对象的 show 方法,并在此基础上添加自己的功能描述,从而得到了 “这是宝马汽车的配置为: 定速巡航功能 自动驾驶功能 车道偏离功能” 这个结果。

这就是装饰器模式的魅力,它允许我们动态地添加和组合功能,而不需要修改原始类的代码

适配器模式

这个适配器有点意思。

在项目中 使用到第三方的插件或者库,但是因为接口不兼容 就需要去添加很多的适配器类。

例如场景:电脑使用一个接口,把桌面演示投影到 投影仪上。

假如:现在有三种类型的接口:VGA HDMI TypeC 。但是目前电脑就用VGA,那么我们就需要用适配器将HDMI转成VGA的格式。把它想成显卡适配器就好理解了。

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
#include <iostream>
#include <string>
#include <memory>

using namespace std;
// 适配器模式:让不兼容的接口可以在一起工作
// 电脑 = 》 投影到 = 》 投影仪上 VGA HDMI TypeC

// VGA接口的电脑,(TV)投影仪也是VGA接口
class VGA // VGA接口 抽象类
{
public:
virtual void play() = 0;
};

// TV01表示支持VGA接口的投影仪
class TV01 : public VGA
{
public:
void play()
{
cout << "通过VGA接口连接投影仪,进行视频播放" << endl;
}
};

// 实现一个电脑类(只支持VGA接口)
class Computer
{
public:
// 由于电脑只支持VGA接口,所以该方法的参数也只能支持VGA接口的指针/引用
void playVideo(VGA *pVGA)
{
pVGA->play();
}
};
/*
方法1:换一个支持HDMI接口的电脑,这个就叫代码重构
方法2:买一个转换头(适配器),能够把VGA信号转成HDMI信号,这个叫添加适配器类
*/

// 进了一批新的投影仪,但是新的投影仪都是只支持HDMI接口
class HDMI
{
public:
virtual void play() = 0;
};
class TV02 : public HDMI
{
public:
void play()
{
cout << "通过HDMI接口连接投影仪,进行视频播放" << endl;
}
};

// 由于电脑(VGA接口)和投影仪(HDMI接口)无法直接相连,所以需要添加适配器类
class VGAToHDMIAdapter : public VGA
{
public:
VGAToHDMIAdapter(HDMI *p) : pHdmi(p) {}
void play() // 该方法相当于就是转换头,做不同接口的信号转换的
{
pHdmi->play();
}

private:
HDMI *pHdmi;
};
int main()
{
Computer computer;
// computer.playVideo(new TV01());
// 不可以直接在里面传入TV02对象的指针,所以需要加上一层转换适配
// 因为playVideo方法接收的还是VGA接口,而适配器也是从VGA 继承而来的
// 适配器类的构造函数接收的是HDMI接口的对象指针 所以这里传入的是newTV02()对象指针
computer.playVideo(new VGAToHDMIAdapter(new TV02()));
return 0;
}

观察者模式

观察者-监听者模式(发布订阅模式),隶属于行为型模式。

行为型模式:主要关注的是对象之间的通信,例如:对象A调用对象B的成员方法等。

观察者-监听者模式:主要处理 对象的一对多的关系,也就是多个对象都依赖于一个对象。当该对象的状态发生改变时,其他对象都可以接受到相应的通知。假如现在一个类表示了一组数据,生成一个对象。通过这同一个数据对象, 可以得到一组 圆饼图、柱状图、条形图等。

简单解释:

这段代码实现的是发布-订阅模式,也称为观察者模式。这是一种在对象之间定义一对多的依赖关系,当一个对象状态改变时,所有依赖于它的对象都会受到通知并被自动更新。

在这个代码中,Observer 是观察者的抽象基类,Observer1Observer2Observer3 都是具体的观察者,他们分别对1、2、3号的消息感兴趣。Subject 是主题类,用来存储每个观察者对哪个消息感兴趣。

当一个消息发生改变时,主题类 Subject 会通过 dispatch 方法找到对这个消息感兴趣的所有观察者,然后调用它们的 upData 方法,通知它们消息已经发生改变。这就是观察者模式的核心。

例如,如果1号消息发生改变,主题类就会找到对1号消息感兴趣的 Observer1Observer2,然后通知他们。如果2号消息发生改变,主题类就会找到对2号消息感兴趣的 Observer1Observer3,然后通知他们。如果3号消息发生改变,主题类就会找到对3号消息感兴趣的 Observer2Observer3,然后通知他们。

这种模式在很多实际应用中都非常有用,比如在GUI编程中,一个按钮的点击事件就可以看作是一个主题,而对这个点击事件感兴趣的所有监听器就是观察者。当用户点击按钮时,所有的监听器都会收到通知。

代码

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <iostream>
#include <string>
#include <unordered_map>
#include <list>
using namespace std;
// 发布订阅模式
// 观察者接收到消息之后,就要去处理消息,更新图像
class Observer // 观察者抽象类
{
public:
// 统一的处理事件,messageid表示消息 回调函数
virtual void upData(int messageid) = 0;
};

// 以下全部是观察者实例
class Observer1 : public Observer
{
public:
void upData(int id)
{
switch (id)
{
case 1:
cout << "观察者1 对1 和 2号消息感兴趣.1号消息发生改变" << endl;
break;
case 2:
cout << "观察者1 对1 和 2号消息感兴趣.2号消息发生改变" << endl;
break;
}
}
};
class Observer2 : public Observer
{
public:
void upData(int id)
{
switch (id)
{
case 1:
cout << "观察者2 对1 和 3号消息感兴趣.1号消息发生改变" << endl;
break;
case 3:
cout << "观察者2 对1 和 3号消息感兴趣.3号消息发生改变" << endl;
break;
}
}
};
class Observer3 : public Observer
{
public:
void upData(int id)
{
switch (id)
{
case 2:
cout << "观察者3 对2 和 3号消息感兴趣.2号消息发生改变" << endl;
break;
case 3:
cout << "观察者3 对2 和 3号消息感兴趣.3号消息发生改变" << endl;
break;
}
}
};

// 主题类
// 需要存储一下 每个Observer对哪个id感兴趣
class Subject
{
public:
// 向list里面添加一个Observer 还有其感兴趣消息id
// 给主题添加观察者对象
void addObserver(Observer *obser, int messageid)
{
// mapping[messageid].push_back(obser);
/*
上面的这一句话
map的【】运算符重载函数
messageid存在 则返回键对应值的引用,插入到list。
键不存在的话,则默认插入一对。(新增加一个messageid,其值为
默认构造的list,把obser添加进去)
*/
unordered_map<int, list<Observer *>>::iterator it = mapping.find(messageid);
if (it != mapping.end())
{
it->second.push_back(obser);
}
else
{
list<Observer *> mylist;
mylist.push_back(obser);
mapping.insert({messageid, mylist});
}
}

// 主题发生改变,给相应的观察者进行广播
void dispatch(int messageid)
{
// 看看 谁对这个消息主题感兴趣
auto it = mapping.find(messageid);
if (it != mapping.end())
{
// 有人对这个消息感兴趣,进行查看list 分发
for (Observer *pObserver : it->second)
{
pObserver->upData(messageid); // 最核心的代码
}
}
}

private:
// 消息id 具体感兴趣的Observer
// 但是对于一个id的消息,可能有多个Observer
// 所以 很多个观察者串成一个列表
unordered_map<int, list<Observer *>> mapping;
};
int main()
{
Subject subject; // 消息主题对象

// 观察者1 对1 和 2号消息感兴趣
subject.addObserver(new Observer1(), 1);
subject.addObserver(new Observer1(), 2);

// 观察者2 对1 和 3号消息感兴趣
subject.addObserver(new Observer2(), 1);
subject.addObserver(new Observer2(), 3);

// 观察者3 对2 和 3号消息感兴趣
subject.addObserver(new Observer3(), 2);
subject.addObserver(new Observer3(), 3);
cout << "************************************" << endl;

int changeMessid;
cout << "请输入一个发生改变的消息 输入-1结束" << endl;
cout << "哪个消息改变了:";
while (cin >> changeMessid && changeMessid != -1)
{
subject.dispatch(changeMessid);
cout << "哪个消息改变了:";
}
return 0;
}

理解成发布-订阅模式

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
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

class Observer
{
public:
virtual void update(const std::string& message) = 0;
virtual ~Observer() {}
};

class StockTicker
{
std::vector<Observer*> observers;
public:
void attach(Observer* obs)
{
observers.push_back(obs);
}

void detach(Observer* obs)
{
observers.erase(std::remove(observers.begin(), observers.end(), obs), observers.end());
}

void notify(const std::string& message)
{
for (Observer* obs : observers)
{
obs->update(message);
}
}

void newPriceUpdate(const std::string& stock, double price)
{
notify("Price updated - " + stock + ": $" + std::to_string(price));
}
};
class Investor : public Observer
{
std::string name;
public:
Investor(const std::string& name) : name(name) {}

void update(const std::string& message) override
{
std::cout << name << " received: " << message << std::endl;
}
};

int main() {
StockTicker ticker;

Investor alice("Alice");
Investor bob("Bob");

ticker.attach(&alice);
ticker.attach(&bob);

ticker.newPriceUpdate("AAPL", 150.50);
ticker.newPriceUpdate("GOOGL", 2729.34);

ticker.detach(&alice);
ticker.newPriceUpdate("AAPL", 151.45);

return 0;
}

应用场景

  1. 发布-订阅系统
  2. 事件处理机制
  3. 实时数据更新
  4. 消息队列系统

享元模式

享元模式是一种结构型设计模式,它通过共享细粒度对象来减少内存使用,以提高性能。该模式主要用于处理大量相似对象的情况,通过共享相同的对象,避免重复创建相似对象,从而节省内存。

在享元模式中,通常会把对象的状态分为两部分:

  1. 内部状态:可以共享的状态,不会随外部环境变化,保存在享元对象内部。
  2. 外部状态:不能共享的状态,会随外部环境变化,需要在对象方法调用时动态传递。

示例场景:字符显示系统

假设你正在开发一个文本编辑器,每个字符都需要保存并显示在屏幕上。如果直接为每个字符创建对象,将占用大量内存。通过享元模式,我们可以共享相同的字符对象。

实现步骤

  1. Flyweight(享元接口): 定义一个方法用于显示字符,同时接收外部状态(即字符的位置)。
  2. ConcreteFlyweight(具体享元类): 实现 Flyweight 接口,包含可以共享的内部状态(如字符的字体、颜色等)。
  3. FlyweightFactory(享元工厂): 维护一个享元对象的共享池,负责返回已存在的共享对象,或者创建一个新对象并放入池中。
  4. Client(客户端): 客户端负责管理外部状态,并在需要时调用享元对象的行为。
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
#include <iostream>
#include <unordered_map>
#include <string>
#include <memory>

// Flyweight(享元接口)
class Character {
public:
virtual void Display(int x, int y) = 0; // 显示字符,并接收外部状态(字符位置)
virtual ~Character() = default;
};

// ConcreteFlyweight(具体享元类)
class ConcreteCharacter : public Character {
private:
char symbol; // 内部状态(可以共享的字符)
public:
ConcreteCharacter(char sym) : symbol(sym) {}

void Display(int x, int y) override {
std::cout << "Character: " << symbol << " at position (" << x << ", " << y << ")\n";
}
};

// FlyweightFactory(享元工厂)
class CharacterFactory {
private:
std::unordered_map<char, std::shared_ptr<Character>> characters; // 享元池
public:
std::shared_ptr<Character> GetCharacter(char symbol) {
if (characters.find(symbol) == characters.end()) {
// 如果字符不存在于池中,创建新对象并加入池中
characters[symbol] = std::make_shared<ConcreteCharacter>(symbol);
}
return characters[symbol];
}
};

// Client(客户端)
int main() {
CharacterFactory factory;

// 创建多个字符对象,并在不同位置显示
std::shared_ptr<Character> charA1 = factory.GetCharacter('A');
charA1->Display(10, 20); // 外部状态:位置 (10, 20)

std::shared_ptr<Character> charA2 = factory.GetCharacter('A');
charA2->Display(30, 40); // 外部状态:位置 (30, 40)

std::shared_ptr<Character> charB = factory.GetCharacter('B');
charB->Display(50, 60); // 外部状态:位置 (50, 60)

// 检查是否共享同一个 'A' 对象
std::cout << "charA1 and charA2 are the same object: "
<< (charA1 == charA2 ? "Yes" : "No") << std::endl;

return 0;
}

责任链模式

责任链模式的目的是避免请求发送者与多个接收者之间的耦合关系,将这些接收者组成一条链,并沿着这条链传递请求,直到有一个接收者处理它为止。

状态模式

状态模式是一种行为型设计模式,它允许对象在内部状态改变时改变其行为。状态模式的关键思想是将对象的行为封装在不同的状态对象中,客户端无需关心状态的切换逻辑,只需调用对象的方法,具体行为由状态对象决定。

状态模式的优点是让状态和行为的变化独立于主对象,从而使得代码更易于维护和扩展。

示例:简单的电灯状态切换

假设我们有一个电灯,它可以处于**开(On)关(Off)**两种状态。我们希望通过状态模式控制电灯的行为(开或关),并能在不同状态下切换。

状态模式的类图:

  • StateLightState
  • ConcreteStateLightOnStateLightOffState
  • ContextLight
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
#include <iostream>
#include <memory>

// State 接口(状态接口)
class LightState {
public:
virtual void PressSwitch() = 0; // 定义一个抽象方法,按下开关时的行为
virtual ~LightState() = default;
};

// ConcreteState:LightOnState(具体状态:开灯状态)
class LightOnState : public LightState {
public:
void PressSwitch() override {
std::cout << "The light is already ON. Turning it OFF now.\n";
}
};

// ConcreteState:LightOffState(具体状态:关灯状态)
class LightOffState : public LightState {
public:
void PressSwitch() override {
std::cout << "The light is OFF. Turning it ON now.\n";
}
};

// Context 类:Light(电灯类)
class Light {
private:
std::unique_ptr<LightState> state; // 持有当前状态的指针
public:
Light() : state(std::make_unique<LightOffState>()) {} // 初始状态是关灯状态

// 设置状态
void SetState(std::unique_ptr<LightState> newState) {
state = std::move(newState);
}

// 用户按下开关
void PressSwitch() {
state->PressSwitch();
// 根据当前状态切换到另一状态
if (dynamic_cast<LightOffState*>(state.get())) {
SetState(std::make_unique<LightOnState>());
} else {
SetState(std::make_unique<LightOffState>());
}
}
};

int main() {
Light light; // 创建电灯,初始为关灯状态

// 用户按下开关多次
light.PressSwitch(); // 输出:The light is OFF. Turning it ON now.
light.PressSwitch(); // 输出:The light is already ON. Turning it OFF now.
light.PressSwitch(); // 输出:The light is OFF. Turning it ON now.
light.PressSwitch(); // 输出:The light is already ON. Turning it OFF now.

return 0;
}

输出结果:

1
2
3
4
The light is OFF. Turning it ON now.
The light is already ON. Turning it OFF now.
The light is OFF. Turning it ON now.
The light is already ON. Turning it OFF now.

代码说明:

  1. 状态接口(LightState):定义了一个纯虚函数 PressSwitch(),表示按下开关时的动作。
  2. 具体状态类(LightOnStateLightOffState):
    • LightOnState:表示灯处于打开状态时的行为。
    • LightOffState:表示灯处于关闭状态时的行为。
  3. 上下文类(Light):
    • 它持有一个当前的状态对象(state),并且通过 PressSwitch() 方法来响应用户操作。
    • 每次按下开关时,电灯会根据当前状态切换到另一个状态。

学习自: