【设计模式】享元模式与策略模式

本文最后更新于:2023年12月17日 下午

Flyweight(享元)模式

1. 意图

运用共享技术有效地支持大量细粒度的对象

2. 动机

Flyweight模式描述了如何共享对象,使得可以细粒度地使用它们而不需要高昂的代价。

Flyweight是一个共享对象,他可以同时在多个场景(context)中使用,并且在每个场景中flyweight都可以作为一个独立的对象——这一点与非共享对象的实例没有区别。flyweight不能对它所运行的场景做出任何假设,这里关键的概念是内部状态外部状态之间的区别。内部状态存储于flyweight中,它包含了独立于flyweight场景的信息,这些信息使得flyweight可以被共享。而外部状态取决于flyweight场景,并根据场景而变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给flyweight。

Flyweight模式对那些通常由于数量太大而难以用对象来表示的概念或实体进行建模。

3. 适用性

Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当一下情况成立时,使用Flyweight模式:

  • 一个应用使用了大量的对象。
  • 完全由于使用大量的对象造成很大的存储开销。
  • 对象的大多数状态都可变为外部状态。
  • 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很对组对象
  • 应用程序不依赖于对象标识。由于Flyweight对象可以被共享,因此概念上明显有别的对象,标识测试将返回真值。

4. 结构

结构示意图

下面对象图说明了如何共享flyweight

对象图

5. 参与者

  • Flyweight(Glyph)
    • 描述一个接口,通过这个接口flyweght可以接受并作用于外部状态
  • ConcreteFlyweight(character)
    • 实现Flyweight接口,并为内部状态(如果有的话)增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的。即它必须独立于ConcreteFlyweight 对象的场景。
  • UnsharedConcreteFlyweight(Row、Column)
    • 并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。在flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。
  • FlyweightFactory
    • 创建并管理Flyweight对象
    • 确保合理地共享Flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或创建一个(如果不存在的话)
  • Client
    • 维持一个对Flyweight的引用
    • 计算或存储一个(或多个)flyweight的外部状态

6. 协作

  • flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象中,而外部状态则是由Client对象存储或计算。当用户调用flyweight对象的操作时,将该状态传递给它。
  • 用户不应该直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象。这可以保证对它们适当地进行共享。

7. 实现

在实现Flyweight模式时,应注意一下几点:

① 删除外部状态。该模式的可用性在很大程度上取决于是否容易识别外部状态并将它从共享对象中删除。

② 管理共享对象。 因为对象是共享的,用户不能直接对它进行实例化,所以FlyweightFactory来帮助用户查找某个特定的flyweight对象。共享还意味着某种形式的引用计数和垃圾回收,这样当一个flyweight不再使用时,可以回收它的存储空间。

8. 示例代码

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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Flyweight.h
// Created by 张宇轩 on 2022/3/26.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;

#ifndef BEHAVIORALPATTERNDESIGN_FLYWEIGHT_H
#define BEHAVIORALPATTERNDESIGN_FLYWEIGHT_H

void test_Flyweight();

// 棋子状态,坐标
struct ChessPoint {
int x;
int y;
ChessPoint() = default;
ChessPoint(int x,int y) : x(x),y(y) {}
string to_string() {
return "(" + ::to_string(x)+"," + ::to_string(y)+")";
}
};

struct ChessPointHash {
size_t operator()(const ChessPoint& point) const {
return hash<int>()(point.x + 10 * point.y);
}
};
struct ChessPointEqual {
bool operator()(const ChessPoint& p1,const ChessPoint& p2 ) const {
return (p1.x == p2.x && p1.y == p2.y);
}
};


// 象棋棋子
class Chess {
public:
enum Type {
Red,
Black,
Board
};
Chess(string name, Type type) :name(name),type(type) {

}

virtual bool move(ChessPoint start, ChessPoint end) {
// 棋盘越界
if (start.x < 0 || start.y < 0 || start.x >= 9 || start.y >= 10 ||
end.x <0 || end.y < 0 || end.x >= 9 || end.y >= 10) {
return false;
}
return true;
}
string get_name() {
return name;
}
Type get_type() {
return type;
}

protected:
string name;
Type type;
};

// 兵
class Soldier : public Chess {
public:
Soldier(string name, Type type) : Chess(name, type) {

}
virtual bool move(ChessPoint start, ChessPoint end) {
if (!Chess::move(start,end)) {
return false;
}
if ( (start.x == end.x && start.y +1 == end.y) || // 向上走
(start.y == end.y && (start.x+1 == end.x || start.x-1 == end.y))){ // 左右走
return true;
}
return false;
}
};

// 车
class Car : public Chess {
virtual bool move(ChessPoint start, ChessPoint end) {
if (!Chess::move(start,end)) {
return false;
}
// 车 没有多余的限制
return true;
}
};

class ChessFactory {
public:
// 如果不提前创建的话,如何根据传入的name,动态创建对应的类(不用if else 进行判断)?
ChessFactory() {
red_chess["兵"] = new Soldier("兵",Chess::Red);
red_chess["车"] = new Soldier("车",Chess::Red);
black_chess["兵"] = new Soldier("兵",Chess::Red);
red_chess["车"] = new Soldier("车",Chess::Red);
}

Chess* create_chess(string name, Chess::Type type) {
if (type == Chess::Type::Red) {
return red_chess[name];
} else {
return black_chess[name];
}
}
~ChessFactory() {
for (auto iter : red_chess) {
delete iter.second;
}
for (auto iter : black_chess) {
delete iter.second;
}
}

private:
unordered_map<string, Chess*> red_chess;
unordered_map<string, Chess*> black_chess;
};


// 象棋棋盘,类比 UnsharedConcreteFlyweight
class ChessBoard : public Chess {
public :
ChessBoard(string name, Type type, ChessFactory& factory) : Chess(name, type) {
// 简单地做下初始化
ChessPoint p1(1,4);
ChessPoint p2(1,8);
board[p1] = factory.create_chess("兵",Red);
board[p2] = factory.create_chess("车",Red);
}

virtual bool move(ChessPoint start, ChessPoint end) {
auto start_iter = board.find(start);
if (start_iter == board.end()) { // 起点没有棋子
cout << "起点没有棋子!" <<endl;
return false;
}
auto end_iter = board.find(end);
if (end_iter != board.end() && // 起点和终点的棋子颜色相同
end_iter->second->get_type() == start_iter->second->get_type()) {
cout << "起点和终点的棋子颜色相同" << endl;
return false;
}
bool flag = start_iter->second->move(start,end);
if (flag) {
board[end] = start_iter->second;
board.erase(start_iter);
cout << "棋子:" << start_iter->second->get_name() <<
" 从" << start.to_string() << "到" << end.to_string() <<" 移动成功" << endl;
} else {
cout << "棋子:" << start_iter->second->get_name() <<
" 从" << start.to_string() << "到" << end.to_string() <<" 移动失败" << endl;
}
return flag;
}

private:
unordered_map<ChessPoint, Chess*, ChessPointHash, ChessPointEqual> board;

};

#endif //BEHAVIORALPATTERNDESIGN_FLYWEIGHT_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Flyweight.cpp
// Created by 张宇轩 on 2022/3/26.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

#include "Flyweight.h"

void test_Flyweight() {
cout << "——————开始测试 Flyweight 模式——————" << endl;
ChessFactory factory;
ChessBoard board("棋盘",Chess::Board, factory);
ChessPoint start1(1,1), start2(1,4),end1(1,8),end2(1,5), end3(1,7);
board.move(start1,end1);
board.move(start2,end1);
board.move(start2,end2);
board.move(end2,end3);
cout << "——————结束测试 Flyweight 模式——————" << endl;
cout << endl ;
}

9. 相关模式

Flyweight模式通常和Composite(组合)模式结合起来,用共享叶节点的有向无环图实现一个逻辑上的层次结构。

通常,最好用flyweight实现State和Strategy对象。

Strategy(策略)模式

1. 意图

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

2. 别名

政策(policy)

3. 适用性

在以下情况下使用Strategy模式:

  • 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
  • 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式。
  • 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

4. 结构

结构示意图

5. 参与者

  • Strategy(策略,如Compositor)
    • 定义所支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法
  • ConcreteStrategy(具体策略,如SimpleCompositor、TeXCompositor、ArrayCompositor)
    • 以Strategy接口实现具体算法
  • Context(上下文,如Composition)
    • 用一个ConcreteStrategy对象来配置
    • 维护一个Strategy对象的引用
    • 可定义一个接口来让Strategy访问它的数据

6. 协作

  • Strategy和Context相互作用以实现选定的算法。当算法被调用时,Context可以将该算法所需要的所有数据都传递给该Strategy。或者,Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context
  • Context将客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrategy对象给该Context,这样客户仅与Context交互。通常有一系列Strategy类可供客户从中选择。

7. 实现

考虑下面的实现问题:

  1. 定义Strategy和Context接口。
  • Strategy和Context接口必须使得ConcreteStrategy能够有效地访问它所需要的context中的任何数据,反之亦然。一种方法是让Context将数据放在参数中传递给Strategy,这使得Strategy和Context解耦,但Context也可能发送一些Strategy不需要的数据。
  • 另一种方法是让Context将自身作为一个参数传递给Strategy,该Strategy再显式地想Context请求数据。或者Strategy可以存储对它的Context的引用。
  1. 将Strategy作为模板参数
  • 在C++中,可利用模板机制用一个Strategy来配置一个类。然而这种方法仅当满足下面条件时才能使用:① 可以在编译时选择Strategy;② 不需要在运行时改变。在这种情况下,要被配置的类(如Context)被定义为一个以Strategy类作为参数的模板类。
  1. 使Strategy对象成为可选的
  • 即使在不使用额外的Strategy对象的情况下,Context也有意义的话,那么它还可以被简化。Context在访问某Strategy前先检查它是否存在。如果有,就使用它;如果没有,那么Context执行缺省的行为。这种方法的好处是客户根本不需要处理Strategy对象,除非不喜欢缺省的行为。

8. 示例代码

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
// Strategy.h
// Created by 张宇轩 on 2022/3/26.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>
#include <string>

using namespace std;
#ifndef BEHAVIORALPATTERNDESIGN_STRATEGY_H
#define BEHAVIORALPATTERNDESIGN_STRATEGY_H

/**
* @brief 策略模式,模拟两个实现
* 1. 定义Strategy和Context接口,模拟场景为 输入框字符串的验证策略
* 2. 将Strategy作为模板参数,可以参考STL中分配器,其中就运用了策略模式
*/

void test_strategy();

// 校验器,对应于 Strategy 类,对字符串进行校验
class Validator {
public:
virtual bool validate(string text) {
if (text.empty()) {
cout << "文本为空!" << endl;
return false;
}
return true;
}
~Validator() = default;
};

// 输入框 对应Context类,需要对输入的字符串进行校验,在不同场景下需要不同的验证策略
class InputText {
public:
InputText() {}
void set_validator(shared_ptr<Validator> validator) {
this->validator = validator;
}
void input_text(string input) {
if (validator->validate(input)) {
cout << "输入:"<< input <<"\t校验成功!" << endl;
text = input;
} else {
cout << "输入:"<< input <<"\t校验失败,请重新输入!" << endl;
}
}
string get_text(){
return text;
}
private:
string text;
std::shared_ptr<Validator> validator;
};

// 数字校验器,校验策略为输入的必须是数字
class DigitValidator : public Validator{
public:
virtual bool validate(string text) {
bool flag = Validator::validate(text);
if (!flag) {
return false;
}
for (char c : text) {
if (c < '0' || c > '9'){
return false;
}
}
return true;
}
};

// 字母校验器,校验策略为输入的必须是字母
class CharacterValidator : public Validator {
public:
virtual bool validate(string text) {
bool flag = Validator::validate(text);
if (!flag) {
return false;
}
for (char c : text) {
if (!( (c >='a' && c<='z') || (c>='A' && c<='Z'))){
return false;
}
}
return true;
}
};


// 然后模拟将Strategy作为模板参数,类比STL容器和分配器的关系,做一个简单地示意
class Allocator {
public:
virtual void allocate() {

}
virtual ~Allocator() = default;
};

// 类比策略类,实现简单内存分批额策略
class SimpleAllocator : public Allocator{
public:
virtual void allocate() {
cout << "实现简单内存分配策略" << endl;
}
};

// 类比策略类,实现内存池分配策略
class PoolAllocator : public Allocator {
public:
virtual void allocate() {
cout << "实现内存池分配策略" << endl;
}
};

template <class T, class Allocator>
class Container {
public:
void push(T val) {
allocator.allocate();
cout << "加入元素: " << val << endl;
}
private:
Allocator allocator;
};

#endif //BEHAVIORALPATTERNDESIGN_STRATEGY_H
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
// Strategy.cpp
// Created by 张宇轩 on 2022/3/26.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

#include "Strategy.h"

void test_strategy() {
cout << "——————开始测试 Strategy 模式——————" << endl;
// 定义接口
InputText it;
it.set_validator(make_shared<DigitValidator>());
it.input_text("1aaaa");
it.set_validator(make_shared<CharacterValidator>());
it.input_text("aaaa");

// 使用模板参数
Container<int,SimpleAllocator> container;
container.push(1);
Container<int,PoolAllocator> container2;
container2.push(2);
cout << "——————结束测试 Strategy 模式——————" << endl;
cout <<endl;
}

9. 相关模式

Flyweight:Strategy对象通常是很好的轻量级对象


【设计模式】享元模式与策略模式
https://2017zhangyuxuan.github.io/2022/03/26/2022-03/2022-03-26 【设计模式】享元和策略模式/
作者
Zhang Yuxuan
发布于
2022年3月26日
许可协议