行为型模式(1)

行为型模式

包括:模版方法模式(Template Method)、命令模式(Command)、观察者模式(Observer)、备忘录模式(Memento)、策略模式(Strategy)

策略模式(Strategy Pattern)

策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类,提供辅助函数.

  • 封装变化: 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  • 针对接口编程,而不是针对实现编程。这里要说明一下,C++的接口指的是抽象类,含有纯虚函数的类
  • 使用了我们的第三个设计原则:多用组合,少用继承。

策略模式

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
// 设计一款模拟鸭子的游戏:游戏中会出现各种鸭子,一遍游泳戏水,一边呱呱叫。
//飞行接口,所有飞行类都实现它,所有新的飞行类都必须实现fly()方法
class FlyBehavior{
public:
virtual void fly()=0;
};
//翅膀飞行类
class FlyWithWings:public FlyBehavior{
public:
virtual void fly(){
//实现鸭子飞行
}
};
//不飞行类
class FlyNoWay:public FlyBehavior{
public:
virtual void fly(){
//什么都不做,鸭子不飞行
}
};
//呱呱叫行为接口,一个接口只包含一个需要实现的quack()方法
class QuackBehavior{
public:
virtual void quack()=0;
};
//真的呱呱叫
class Quack:public QuackBehavior{
public:
virtual void quack(){//实现鸭子呱呱叫}
};
//吱吱叫
class Squeak:public QuackBehavior{
public:
virtual void quack(){//实现鸭子吱吱叫}
};
//不叫
class MuteQuack:public QuackBehavior{
public:
virtual void quack(){//什么都不做,不会叫}
};
//橡胶鸭类
class RubberDuck {
private:
FlyBehavior* flyBehavior;
QuackBehavior* quackBehavior;
public:
RubberDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Squeak();
}
//飞行行为
void performFly() {
flyBehavior->fly();
}
//改变飞行行为
void setFlyBehavior(FlyBehavior* flyB) {
flyBehavior = flyB;
}
//呱呱叫行为
void performQuack() {
quackBehavior->quack();
}
//改变叫声
void setPerformQuack(QuackBehavior* quackB) {
quackBehavior = quackB;
}
//其他行为方法及析构函数
...
};

观察者模式(Observer Pattern)

观察者模式(Observer Pattern)属于行为型模式,在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。
观察者模式

  • Subject:抽象主题(抽象被观察者),抽象主题对象把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),将有关状态存入具体观察者对象,在具体主题内部状态发生改变时,给所有注册的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,是实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

假设现在需要完成公司的一个项目,气象站项目。气象站的数据由WeatherData对象提供,包括温度、湿度和气压。项目要求有两种布告板,分别显示目前状况(CurrentStatus)和气象统计(Statistics)。当WeatherData对象获得最新的测量数据时,两种布告板必须实时更新。而且,这是一个可以扩展的气象站,需要公布一组API,好让其他开发人员可以写出自己的布告板。

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
// 抽象观察者(Observer)。里面定义了一个更新的方法:
class Observer {
public:
virtual void update(float temperature,float humidity,float pressure)=0;
};
// 具体观察者(Concrete Observer)。布告板是观察者,里面实现了更新的方法:
//目前状况布告板:CurrentStatus
class CurrentStatus:public Observer{
...
public:
//具体实现更新接口函数
void update(float temperature,float humidity,float pressure){
//同上
}
//显示当前气象信息
void display(){...} //同上
};
//气象信息统计布告板:Statistics
class Statistics:public Observer{
...
public:
//具体实现更新接口函数
void update(float temperature,float humidity,float pressure{
//同上
}
//显示气象统计信息
void display(){...} //同上
}
// 抽象被观察者(Subject)。抽象主题提供了注册attach、移除detach和通知notify三个纯虚函数,供具体被观察者实现:
class Subject{
public:
virtual void attach(Observer*)=0;
virtual void detach(Observer*)=0;
virtual void notify()=0;
};
// 具体被观察者(Concrete Subject)。这里的WeatherData类是具体主题(具体被观察者),具体实现如下:
class WeatherData:public Subject{
float temperature=0;
float humidity=0;
float pressure=0;
list<Observer*> list; //用于记录注册的观察者
public:
//实现注册
void attach(Observer* o){
list.push_back(o);
}
//实现移除
void detach(Observer* o){
for(auto it=list.begin();it!=list.end();++it){
if(*it==o){
list.erase(it);
break;
}
}
}
//实现通知所有的观察者
void notify(){
for(auto it=list.begin();it!=list.end();++it){
(*it)->update(temperature,humidity,pressure);
}
}
//设置观测值
void setMeasurements(float temperature,float humidity,float pressure){
this->temperature= temperature;
this->humidity= humidity;
this->pressure= pressure;
this->notify();//观测值更新,通知观察者
}
//WeatherData的其他方法
};

使用场景:

  • (1)关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
  • (2)事件多级触发场景。跨系统的消息交换场景,如消息队列、事件总线的处理机制。

命令模式(Command Pattern)

命令模式(Command Pattern)属行为型,将请求封装成对象,以便使用不同的请求、请求日志或请求队列等来参数化其他对象。命令模式也支持撤销操作。通过增加一个命令对象,放在请求者与请求的接收者之间,来达到二者的解耦合。一个请求对应一个命令对象,命令对象将请求的接收者和完成请求的基本操作封装在一起,对外提供execute()方法。完成请求的基本操作由接收者提供,在execute()方法中被调来完成请求,请求者如果调用execute(),请求的目的就能达到,而不需要直接与请求的接收者交互,降低了耦合度。

命令模式

  • Client类:最终的客户端调用类。
  • Invoker类:调用者,调用具体命令类提供的execute()方法来完成请求。
  • Command类:是一个抽象类,一般需要对外公布一个execute()方法来完成请求。
  • ConcreteCommand类:Command类的实现类,对抽象类中声明的execute()方法进行实现。
  • Receiver类:接收者,动作的执行者,对外提供基本操作,交由ConcreteCommand类调用,来完成Invoker类的请求。

去餐厅吃饭的流程一般是这样的,服务员叫消费者点餐,下单生成订单,订单送往厨房交由厨师烹饪。四个角色,消费者对应Client类,服务员对应Invoker类,订单对应ConcreteCommand类,厨师对应Receiver类。服务员通过订单,无需自己完成食物的烹饪,而是交由厨师来完成,所以服务员是烹饪食物该动作的请求者,订单充当命令的角色,厨师是完成食物烹饪的动作执行者。

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
// 动作执行者厨师类:
class Chef {
public:
void cook() const{
cout << "开始烹饪"<< endl;
}
};
// 充当命令角色的订单类:
//抽象命令类
class Command{
public:
virtual void execute() const = 0;
};
//订单类
class OrderCommand:public Command{
private:
const Chef &chef;
public:
OrderCommand(const Chef & chef):chef(chef){}
virtual void execute() const{
chef.cook();
}
};
// 烹饪动作请求者服务员类:
class Attendant {
private:
const OrderCommand & orderCommand;
public:
Attendant(const OrderCommand & orderCommand):orderCommand(orderCommand){}
void cook() {
orderCommand.execute();
}
};

应用场景:
对于大多数请求-响应模式的功能,比较适合使用命令模式,正如命令模式定义说的那样,命令模式对实现记录日志、撤销操作等功能比较方便。

模板方法模式(Template Method Pattern)

模板方法模式(Template Method Pattern)属行为型,在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中,使子类可以不改变算法结构即可重定义算法的某些特定步骤。

模板方法模式

  • AbstractClass:实现了模板方法,定义了算法骨架。
  • ConcreteClass:实现抽象类中的抽象方法,完成完整的算法。

一个武侠要战斗的时候,也有一套固定的模式,那就是运行内功、开通经脉、准备武器和使用招式。

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
// 这个抽象类包含了三种类型的方法,分别是抽象方法、具体方法和钩子方法。
// 抽象方法是交由子类去实现,具体方法则在父类实现了子类公共的方法实现,在上面的例子就是武侠开通经脉的方式都一样,所以就在具体方法中实现。钩子方法则分为两类,第一类是hook(),它有一个空实现的方法,子类可以视情况来决定是否要覆盖它;第二类则是hasWeapons(),这类钩子方法的返回类型通常是bool类型的,一般用于对某个条件进行判断,如果条件满足则执行某一步骤,否则将不执行。
class AbstractSwordsman {
public:
void fighting(){
//运行内功,抽象方法
neigong();
//调整经脉,具体方法
meridian();
//如果有武器则准备武器
if (hasWeapons()) {
weapons();
}
//使用招式
moves();
//钩子方法
hook();//用于判断是否在算法骨架中是否执行某一算法
}
protected:
virtual void neigong() = 0;
virtual void weapons() = 0;
virtual void moves() = 0;
virtual void meridian() {
cout << "开通正经与奇经" << endl;
}
//是否有武器,默认是有武器的,钩子方法
virtual bool hasWeapons() {
return true;
}
//钩子方法实现为空
virtual void hook(){}
};
// concrete
class ZhangWuJi:public AbstractSwordsman {
protected:
// @ Override
void neigong() {
cout << "运行九阳神功" << endl;
}
// @ Override
void weapons(){} //实现为空
// @ Override
void moves() {
cout << "使用招式乾坤大挪移"<< endl;
}
// @ Override
bool hasWeapons() {
return false;
}
};

应用场景:

  • (1)各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
  • (2)面对重要复杂的算法,可以把核心算法设计为模版方法,周边相关细节功能交由各个子类实现。
  • (3)需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

备忘录模式

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态
备忘录模式

  • 发起人:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
  • 备忘录:负责存储发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
  • 管理角色:对备忘录进行管理,保存和提供备忘录。

Memento类定义了内部的状态,而Caretake类是一个保存进度的管理者,GameRole类是游戏角色类。可以看到GameRole的对象依赖于Memento对象,而与Caretake对象无关。

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
//需保存的信息
class Memento
{
public:
int m_vitality; //生命值
int m_attack; //进攻值
int m_defense; //防守值
public:
Memento(int vitality, int attack, int defense):
m_vitality(vitality),m_attack(attack),m_defense(defense){}
Memento & operator=(const Memento &memento)
{
m_vitality = memento.m_vitality;
m_attack = memento.m_attack;
m_defense = memento.m_defense;
return *this;
}
};
//游戏角色
class GameRole
{
private:
int m_vitality;
int m_attack;
int m_defense;
public:
GameRole(): m_vitality(100),m_attack(100),m_defense(100) {}
Memento Save() //保存进度,只与Memento对象交互,并不牵涉到Caretake
{
Memento memento(m_vitality, m_attack, m_defense);
return memento;
}
void Load(Memento memento) //载入进度,只与Memento对象交互,并不牵涉到Caretake
{
m_vitality = memento.m_vitality;
m_attack = memento.m_attack;
m_defense = memento.m_defense;
}
void Show() { cout<<"vitality : "<< m_vitality<<", attack : "<< m_attack<<", defense : "<< m_defense<< endl; }
void Attack() { m_vitality -= 10; m_attack -= 10; m_defense -= 10; }
};
//保存的进度库
class Caretake
{
public:
Caretake() {}
void Save(Memento menento) { m_vecMemento.push_back(menento); }
Memento Load(int state) { return m_vecMemento[state]; }
private:
vector<Memento> m_vecMemento;
};
// 客户使用方式:
//测试案例
int main()
{
Caretake caretake;
GameRole role;
role.Show(); //初始值
caretake.Save(role.Save()); //保存状态
role.Attack();
role.Show(); //进攻后
role.Load(caretake.Load(0)); //载入状态
role.Show(); //恢复到状态0
return 0;
}