一、责任链模式

1.1 职责链模式简介

职责链模式又叫责任链模式。很多情况下,可以处理某个请求的对象可能不止一个,请求可以沿着某一条对象之间形成的关系一级一级由下家传递到上家,形成一条链——职责链。职责链可以是直线,也可以是环或树形结构。常见的职责链形式是直线。链上的每一个对象都是请求的处理者,客户端要做的仅仅是发送请求,不需要关心请求的处理细节过程。由此,职责链模式将请求者和请求的接收者解耦。

职责链模式定义如下:

职责链模式:

避免将一个请求的发送者和接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。

1.2 职责链模式结构

职责链模式的UML结构如下图所示,职责链模式的核心在于引入了一个抽象处理者:

职责链模式中一共包含两个角色:

  • Handler(抽象处理者):抽象处理者一般为抽象类,声明了一个处理请求的接口handleRequest(),定义了一个抽象处理者类型的对象,作为其对下家的引用,通过该引用可以形成一条责任链。
  • ConcreteHandler(具体处理者)
    是抽象处理者的子类,实现了处理请求的接口。在具体的实现中,如果该具体处理者能够处理该请求,就处理它,否则将该请求转发给后继者。具体处理者可以访问下一个对象。

由上述可知,在职责链模式中很多对象由每一个对象对其下家的引用连接起来形成一条链条,请求在这个链条上逐级传递,知道某一级能够处理这个请求为止。客户端不知道也不必知道是哪一级处理者处理了该请求,因为每个处理者都有相同的接口handleRequest()。接下来通过一个实例来进一步认识职责链模式。

1.3 职责链模式代码实例

以报销的例子为例,对于不同金额的票据,公司不同级别的领导处理情况如下:

金额0~10万:组长可处理

金额10~30万:主管处理

金额30~60万:经理处理

金额超过60万:老板处理

本节Jungle将用C++模拟该过程。该实例UML图如下:

1.3.1 票据类

// 请求:票据
class Bill
{public:Bill(){}Bill(int iId, string iName, double iAccount){id = iId;name = iName;account = iAccount;}double getAccount(){return this->account;}void print(){printf("\nID:\t%d\n", id);printf("Name:\t%s\n", name.c_str());printf("Account:\t%f\n", account);}
private:int id;string name;double account;
};

1.3.2 抽象处理者

// 抽象处理者
class Approver
{public:Approver(){}Approver(string iName){setName(iName);}// 添加上级void setSuperior(Approver *iSuperior){this->superior = iSuperior;}// 处理请求virtual void handleRequest(Bill*) = 0;string getName(){return name;}void setName(string iName){name = iName;}
protected:Approver *superior;
private:string name;
};

1.3.3 具体处理者

具体处理者:组长

// 具体处理者:组长
class GroupLeader :public Approver
{public:GroupLeader(){}GroupLeader(string iName){setName(iName);}// 处理请求void handleRequest(Bill *bill){if (bill->getAccount() < 10){printf("组长 %s 处理了该票据,票据信息:",this->getName().c_str());bill->print();}else{printf("组长无权处理,转交上级……\n");this->superior->handleRequest(bill);}}
};

具体处理者:主管

// 具体处理者:主管
class Head :public Approver
{public:Head(){}Head(string iName){setName(iName);}// 处理请求void handleRequest(Bill *bill){if (bill->getAccount() >= 10 && bill->getAccount()<30){printf("主管 %s 处理了该票据,票据信息:", this->getName().c_str());bill->print();}else{printf("主管无权处理,转交上级……\n");this->superior->handleRequest(bill);}}
};

具体处理者:经理

// 具体处理者:经理
class Manager :public Approver
{public:Manager(){}Manager(string iName){setName(iName);}// 处理请求void handleRequest(Bill *bill){if (bill->getAccount() >= 30 && bill->getAccount()<60){printf("经理 %s 处理了该票据,票据信息:", this->getName().c_str());bill->print();}else{printf("经理无权处理,转交上级……\n");this->superior->handleRequest(bill);}}
};

具体处理者:老板

// 具体处理者:老板
class Boss :public Approver
{public:Boss(){}Boss(string iName){setName(iName);}// 处理请求void handleRequest(Bill *bill){printf("老板 %s 处理了该票据,票据信息:", this->getName().c_str());bill->print();}
};

1.3.4 客户端代码示例

客户端创建了四个角色,分别是组长、主管、经理和老板,并设置了上下级关系。然后创建了4张票据,金额不等,都先统一交给组长处理。

#include <iostream>
#include "ChainOfResponsibility.h"int main()
{Approver *zuzhang, *bingge, *chunzong, *laoban;zuzhang = new GroupLeader("孙大哥");bingge = new Head("兵哥");chunzong = new Manager("春总");laoban = new Boss("张老板");zuzhang->setSuperior(bingge);bingge->setSuperior(chunzong);chunzong->setSuperior(laoban);// 创建报销单Bill *bill1 = new Bill(1, "Jungle", 8); Bill *bill2 = new Bill(2, "Lucy", 14.4);Bill *bill3 = new Bill(3, "Jack", 32.9);Bill *bill4 = new Bill(4, "Tom", 89);// 全部先交给组长审批zuzhang->handleRequest(bill1); printf("\n");zuzhang->handleRequest(bill2); printf("\n");zuzhang->handleRequest(bill3); printf("\n");zuzhang->handleRequest(bill4);printf("\n\n");system("pause");return 0;
}

1.3.5 运行结果

运行结果如下图,可以看到,针对不同金额的票据,处理请求在不同职级之间层层上报,成功模拟了引言中的过程。

1.4 总结

优点:

  • 将请求的接收者和处理者解耦,客户端无需知道具体处理者,只针对抽象处理者编程,简化了客户端编程过程,降低系统耦合度;

  • 在系统中增加一个新的处理者时,只需要继承抽象处理者,重新实现handleRequest()接口,无需改动原有代码,符合开闭原则;
    给对象分配职责时,职责链模式赋予系统更多灵活性。

缺点:

  • 请求没有一个明确的接收者,有可能遇到请求无法响应的问题;
  • 比较长的职责链,其处理过程会很长。
  • 建立职责链的工作是在客户端进行,如果建立不当,可能导致循环调用或者调用失败。

适用环境:

  • 有多个对象处理同一个请求,具体由谁来处理是在运行时决定,客户端只需发出请求到职责链上,而无需关心具体是谁来处理;
    可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变职责链中各个处理者之间的上下级关系。

二、命令模式

2.1 命令模式简介

命令模式可以将请求(命令)的发送者与接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道请求是如何完成的。下面是比较晦涩难懂的命令模式的定义:

命令模式:

将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。

命令模式的定义比较复杂,也提到一些术语。这些将在下面的阐述和举例中做进一步说明。

2.2 命令模式结构


命令模式的UML结构如上图,命令模式一共有以下几种角色:

  • Command(抽象命令类):是一个抽象类,声明了用于执行命令的接口execute()。
  • ConcreteCommand(具体命令类):具体的命令类,实现了执行命令的接口execute(),它对应具体的接收者对象,将接收者(Receiver)的动作action()绑定其中。在execu()方法中将调用接收者的动作action()。(这就是定义中的“将请求封装成一个对象”的体现)
  • Invoker(调用者):请求的发送者,通过命令对象来执行请求。一个调用者不需要在设计时确定其接收者,所以调用者通过聚合,与命令类产生关联。具体实现中,可以将一个具体命令对象注入到调用者中,再通过调用具体命令对象的execute()方法,实现简介请求命令执行者(接收者)的操作。
  • Receiver(接收者): 实现处理请求的具体操作(action)。

2.3 命令模式代码实例

房间中的开关(Button)就是命令模式的一个实现,本例使用命令模式来模拟开关功能,可控制的对象包括电灯(Lamp)和风扇(Fan)。用户每次触摸(touch)开关,都可以打开或者关闭电灯或者电扇。


本实例的UML图如上所示。抽象命令类仅声明execute()接口。有两个具体命令类,分别是控制灯的LampCommand和控制风扇的FanCommand类,两个具体类中实现了execute()接口,即执行开关灯/风扇请求。本例中的调用者是按钮Button,每次用户触摸touch())开关按钮,即是在发送请求。本例具体设计实现过程如下。

2.3.1 接收者类:电灯和风扇

// 接收者:电灯类
class Lamp
{public :Lamp(){this->lampState = false;}void on(){lampState = true;printf("Lamp is on\n");}void off(){lampState = false;printf("Lamp is off\n");}bool getLampState(){return lampState;}
private:bool lampState;
};// 接收者:风扇类
class Fan
{public:Fan(){this->fanState = false;}void on(){fanState = true;printf("Fan is on\n");}void off(){fanState = false;printf("Fan is off\n");}bool getFanState(){return fanState;}
private:bool fanState;
};

2.3.2 抽象命令类

// 抽象命令类 Command
class Command
{public:Command(){}// 声明抽象接口:发送命令virtual void execute() = 0;
private:Command *command;
};

2.3.3 具体命令类

// 具体命令类 LampCommand
class LampCommand :public Command
{public:LampCommand(){printf("开关控制电灯\n");lamp = new Lamp();}// 实现execute()void execute(){if (lamp->getLampState()){lamp->off();}else{lamp->on();}}
private:Lamp *lamp;
};// 具体命令类 FanCommand
class FanCommand :public Command
{public:FanCommand(){printf("开关控制风扇\n");fan = new Fan();}// 实现execute()void execute(){if (fan->getFanState()){fan->off();}else{fan->on();}}
private:Fan *fan;
};

2.3.4 调用者:Button

// 调用者 Button
class Button
{
public:
Button(){}
// 注入具体命令类对象
void setCommand(Command *cmd){
this->command = cmd;
}
// 发送命令:触摸按钮
void touch(){
printf(“触摸开关:”);
command->execute();
}
private:
Command *command;
};

2.3.5 客户端代码示例

#include <iostream>
#include "CommandPattern.h"int main()
{// 实例化调用者:按钮Button *button = new Button();Command *lampCmd, *fanCmd;// 按钮控制电灯lampCmd = new LampCommand();button->setCommand(lampCmd);button->touch();button->touch();button->touch();printf("\n\n");// 按钮控制风扇fanCmd = new FanCommand();button->setCommand(fanCmd);button->touch();button->touch();button->touch();printf("\n\n");system("pause");return 0;
}

2.3.6 运行结果

可以看到,客户端只需要有一个调用者和抽象命令类,在给调用者注入命令时,再将命令类具体化(这也就是定义中“可用不同的请求对客户进行参数化”的体现)。客户端并不知道命令是如何传递和响应,只需发送命令touch()即可,由此实现命令发送者和接收者的解耦。

如果系统中增加了新的功能,功能键与新功能对应,只需增加对应的具体命令类,在新的具体命令类中调用新的功能类的action()方法,然后将该具体命令类通过注入的方式加入到调用者,无需修改原有代码,符合开闭原则。

2.4 命令队列

有时候,当请求发送者发送一个请求时,有不止一个请求接收者产生响应(Qt信号槽,一个信号可以连接多个槽),这些请求接收者将逐个执行业务方法,完成对请求的处理,此时可以用命令队列来实现。比如按钮开关同时控制电灯和风扇,这个例子中,请求发送者是按钮开关,有两个接收者产生响应,分别是电灯和风扇。

可以参考的命令队列的实现方式是增加一个命令队列类(CommandQueue)来存储多个命令对象,不同命令对象对应不同的命令接收者。调用者也将面对命令队列类编程,增加注入具体命令队列类对象的方法setCommandQueue(CommandQueue *cmdQueue)。

下面的例子展示了按钮开关请求时,电灯和风扇同时作为请求的接收者。代码如下所示:

#ifdef COMMAND_QUEUE
/*************************************/
/*             命令队列              */
#include <vector>// 命令队列类
class CommandQueue
{public:CommandQueue(){}void addCommand(Command *cmd){commandQueue.push_back(cmd);}void execute(){for (int i = 0; i < commandQueue.size(); i++){commandQueue[i]->execute();}}
private:vector<Command*>commandQueue;};// 调用者
class Button2
{public:Button2(){}// 注入具体命令队列类对象void setCommandQueue(CommandQueue *cmdQueue){this->cmdQueue = cmdQueue;}// 发送命令:触摸按钮void touch(){printf("触摸开关:");cmdQueue->execute();}
private:CommandQueue *cmdQueue;
};#endif

客户端代码如下:

#ifdef COMMAND_QUEUEprintf("\n\n***********************************\n");Button2 *button2 = new Button2();Command *lampCmd2, *fanCmd2;CommandQueue *cmdQueue = new CommandQueue();// 按钮控制电灯lampCmd2 = new LampCommand();cmdQueue->addCommand(lampCmd2);// 按钮控制风扇fanCmd2 = new FanCommand();cmdQueue->addCommand(fanCmd2);button2->setCommandQueue(cmdQueue);button2->touch();#endif

效果如下图:

2.5 命令模式其他应用

2.5.1 记录请求日志

将历史请求记录保存在日志里,即请求日志。很多软件系统都提供了日志文件,记录运行过程中的流程。一旦系统发生故障,日志成为了分析问题的关键。日志也可以保存命令队列中的所有命令对象,每执行完一个命令就从日志里删除一个对应的对象。

2.5.2 宏命令

宏命令又叫组合命令,是组合模式和命令模式的结合。宏命令是一个具体命令类,拥有一个命令集合,命令集合中包含了对其他命令对象的引用。宏命令通常不直接与请求者交互,而是通过它的成员来遍历调用接收者的方法。当调用宏命令的execute()方法时,就遍历执行每一个具体命令对象的execute()方法。(类似于前面的命令队列)

2.6 总结

优点:

  • 降低系统耦合度,将命令的请求者与接收者分离解耦,请求者和发送者不存在直接关联,各自独立互不影响。
  • 便于扩展:新的命令很容易加入到系统中,且符合开闭原则。
  • 较容易实现命令队列或宏命令。
  • 为请求的撤销和回复操作提供了一种设计实现方案。

缺点:

  • 命令模式可能导致系统中有过多的具体命令类,增加了系统中对象的数量。

适用环境:

  • 系统需要将请求发送者和接收者解耦,使得发送者和接收者互不影响。
  • 系统需要在不同时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销和恢复操作。
  • 系统需要将一组操作组合在一起形成宏命令。

三、迭代器模式

写代码不少使用数组或者类似的集合对象吧?每次要遍历一遍数组怎么办?For 循环!或者while循环,一个一个访问每个位置的元素,直到数组末尾。STL里面甚至有专门的迭代器,针对具体的集合类对象,有对应使用的迭代器。STL的迭代器提供了丰富的遍历方法,如访问集合对象的首位元素、末位元素、指定位置的元素、下一个元素……怎么样,是不是感觉有了迭代器,遍历方法不再是难事了?

3.1 迭代器模式概述

遍历在日常编码过程中经常使用,通常是需要对一个具有很多对象实例的集合(称为聚合对象)进行访问或获取。比如要取聚合对象的首位元素、判断是否在聚合对象的末尾等。针对聚合对象的遍历,迭代器模式是一种很有效的解决方案,也是一种使用频率很高的设计模式。

迭代器模式:

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

通过引入迭代器,可以将数据的遍历功能从聚合对象中分离出来,这样一来,聚合对象只需负责存储数据,而迭代器对象负责遍历数据,使得聚合对象的职责更加单一,符合单一职责原则。

3.2 迭代器模式结构

迭代器模式结构中包含聚合和迭代器两个层次的结构。为方便扩展,迭代器模式常常和工厂方法模式结合。迭代器模式的UML图如下。有图可知,迭代器模式有以下几个角色:

  • Iterator(抽象迭代器):声明了访问和遍历聚合对象元素的接口,如first()方法用于访问聚合对象中第一个元素,next()方法用于访问下一个元素,hasNext()判断是否还有下一个元素,currentItem()方法用于获取当前元素。
  • ConcreteIterator(具体迭代器):实现抽象迭代器声明的方法,通常具体迭代器中会专门用一个变量(称为游标)来记录迭代器在聚合对象中所处的位置。
  • Aggregate(抽象聚合类):用于存储和管理元素对象,声明一个创建迭代器的接口,其实是一个抽象迭代器工厂的角色。
  • ConcreteAggregate(具体聚合类):实现了方法createIterator(),该方法返回一个与该具体聚合类对应的具体迭代器ConcreteIterator的实例。

3.3 迭代器模式代码实例

电视机遥控器是迭代器的一个现实应用,通过它可以实现对电视频道集合的遍历操作,电视机可以看成一个存储频道的聚合对象。本例Jungle将采用迭代器模式来模拟遥控器操作电视频道的过程。

很明显,遥控器是一个具体的迭代器,具有上一个频道previous() 、下一个频道next()、当前频道currentChannel()等功能;需要遍历的聚合对象是电视频道的集合,即电视机。本例的UML图如下:

3.3.1 抽象聚合类和具体聚合类

#ifndef __AGGREGATE_H__
#define __AGGREGATE_H__#include <vector>
using namespace std;// 前向声明,因为两个类互相引用
class Iterator;
class RemoteControl;// 抽象聚合类 Aggregate
class Aggregate
{public:Aggregate(){}virtual Iterator* createIterator() = 0;
};// 具体聚合类 Television
class Television :public Aggregate
{public:Television();Television(vector<string> iChannelList);// 实现创建迭代器Iterator* createIterator();// 获取总的频道数目int getTotalChannelNum();void play(int i);
private:vector<string> channelList;
};#endif //__AGGREGATE_H__

实现:

#include "Iterator.h"Television::Television(){}Television::Television(vector<string> iChannelList){this->channelList = iChannelList;
}Iterator* Television::createIterator(){RemoteControl *it = new RemoteControl();it->setTV(this);return (Iterator*)it;
}int Television::getTotalChannelNum(){return channelList.size();
}void Television::play(int i){printf("现在播放:%s……\n", channelList[i].c_str());
}

3.3.2 抽象迭代器

// 抽象迭代器
class Iterator
{public:Iterator(){}// 声明抽象遍历方法virtual void first() = 0;virtual void last() = 0;virtual void next() = 0;virtual void previous() = 0;virtual bool hasNext() = 0;virtual bool hasPrevious() = 0;virtual void currentChannel() = 0;
};

3.3.3 具体迭代器:RemoteControl

// 遥控器:具体迭代器
class RemoteControl :public Iterator
{public:RemoteControl(){}void setTV(Television *iTv){this->tv = iTv;cursor = -1;totalNum = tv->getTotalChannelNum();}// 实现各个遍历方法void first(){cursor = 0;}void last(){cursor = totalNum - 1;}void next(){cursor++;}void previous(){cursor--;}bool hasNext(){return !(cursor == totalNum);}bool hasPrevious(){return !(cursor == -1);}void currentChannel(){tv->play(cursor);}
private:// 游标int cursor;// 总的频道数目int totalNum;// 电视Television* tv;
};

3.3.4 客户端代码示例及结果

#include <iostream>
#include "Iterator.h"int main()
{vector<string> channelList = { "新闻频道", "财经频道", "体育频道", "电影频道", "音乐频道", "农业频道", "四川卫视", "成都卫视" };// 创建电视Television *tv = new Television(channelList);// 创建遥控器Iterator *remoteControl = tv->createIterator();// 顺序遍历printf("顺序遍历:\n");remoteControl->first();// 遍历电视所有频道while (remoteControl->hasNext()){remoteControl->currentChannel();remoteControl->next();}printf("\n\n");// 逆序遍历printf("逆序遍历:\n");remoteControl->last();// 遍历电视所有频道while (remoteControl->hasPrevious()){remoteControl->currentChannel();remoteControl->previous();}printf("\n\n");system("pause");return 0;
}

3.3.5 运行结果

3.4 总结

观察上述代码可发现,迭代器类和聚合类存在相互包含相互引用的关系,因此代码里需要前向声明某个类。

优点:

  • 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多个遍历方式。

  • 简化了聚合类,使得聚合类的职责更加单一;

  • 迭代器模式中引入抽象层,易于增加新的迭代器类,便于扩展,符合开闭原则。
    缺点:

  • 将聚合类中存储对象和管理对象的职责分离,增加新的聚合类时同样需要考虑增加对应的新的迭代器类,类的个数成对增加,不利于系统管理和维护;

  • 设计难度较大,需要充分考虑将来系统的扩展。
    适用环境:

以下场景可以考虑使用迭代器模式:

  • 访问一个聚合对象而无需暴露它的内部结构;
  • 需要为一个聚合对象提供多种遍历方法。

四、中介者模式

离开学校参加工作之前,你一定是有一段时间是在找租。

为了找到合适的房子,沿着地铁线一个小区一个小区的去问门卫问保安,或者照着小区门口展板上的房东的联系方式去找房东……此事已经过去大半年了,但现在想来还是觉得很麻烦!麻烦在哪里?得亲自走亲自联系各个房东,通信录和微信得加好多房东……

其实有更省事的办法,那就是找中介,租房中介哪儿都是。虽然贵(主要原因),但是的确为租客省了很多事,其实也为房东省了很多事。

4.1 中介者模式简介

上述租房的例子如上图,如果自己去租房,得和各个房东亲自交互,如果另一个租客贱萌兔也在自己找房,同样也得和很多房东打交道。房东也是一样,得和众多不同的租客联系。如果有中介者了,房东们只需要去中介者那里注册一下,自己的房子在哪儿、什么户型设施、价格多少,就ok了;Jungle和贱萌兔也只需要和一个人打交道,那就是中介。中介的出现使两边都省去了不少事。

软件设计模式中,也有一种类似的解决方案,那就是中介者模式——

中介者模式:

定义一个对象来封装一系列对象的交互。中介者模式使各个对象之间不需要显示地相互引用,从而使其耦合松散,而且用户可以独立地改变它们之间的交互。

如果一个系统里各个对象之间存在多对多的相互关系,可以将对象之间的一些交互行为从各个对象中分离出来,集中封装在一个中介者对象中,使其耦合松散,并由中介者统一协调。通过中介者,对象之间的多对多关系就简化了相对更简单的一对多关系。

4.2 中介者模式结构

中介者模式的UML图如下,为了便于扩展,系统引入了抽象中介者。

由图可知,中介者模式主要有以下角色:

  • Mediator(抽象中介者):声明一个用于与各个同事对象之间交互的接口,通常声明一个注册方法,用于增加同事对象;
  • ConcreteMediator(具体中介者):实现上面的接口,协调各个同事对象来实现协作行为,维持对各个同事对象的引用;
  • Colleague(抽象同事类):声明各个同事类公有的接口,同时维持了一个对抽象中介者类的引用;
  • ConcreteColleague(具体同事类): 具体实现接口,具体同事类只需与中介者通信,通过中介者完成与其他同事类的通信。

中介者模式的核心在于引入了中介者类,中介者类承担了两个层次的职责:

  • 结构上起中转作用:通过中介者的中转,各个同事之间不必再相互显示调用或引用,只需通过中介者实现间接调用的目的;
  • 行为上起协调作用:中介者可以进一步地将同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不必指出中介者具体该如何操作,中介者根据封装在自身内部的协调逻辑对同事的请求进一步处理,将同事成员之间的关系行为进行分离和封装。

4.3 中介者模式代码实例

本节将采用中介者模式模拟“租客——房租中介——房东”之间的关系!

Jungle和贱萌兔想要通过房屋中介(Agency)租房,需要去中介处了解房东(Landlord)的信息(姓名,价格,地址和联系方式);房东们(Landlord)需要在中介处注册自己的房源,同时也可以从中介处了解租客(Tenant)的信息(姓名)。

本例的UML图如下:

4.3.1 公共头文件

为区分房东和租客,Jungle定义了一个枚举类型和对应的setter、getter方法:

#ifndef __COMMON_H__
#define __COMMON_H__// 公共头文件#include <vector>
using namespace std;enum PERSON_TYPE
{NONE_PERSON,LANDLORD,TENANT
};#endif  //__COMMON_H__

4.3.2 中介者

抽象中介者

// 抽象中介者
class Mediator
{public:Mediator(){}// 声明抽象方法virtual void operation(Colleague*) = 0;// 声明注册方法virtual void registerMethod(Colleague*) = 0;
};

具体中介者Agency
具体中介者就是真实的中介对象类,他手里有房东的名单(landlordList)和租客名单(tenantList),房东和租客通过registerMethod()在中介处登记注册。同时,房东可以询问中介租客信息,租客也可以向中介询问房东信息。

// 具体中介者
class Agency:public Mediator
{public:Agency(){}void registerMethod(Colleague* person){switch (person->getPersonType()){case LANDLORD:landlordList.push_back((Landlord*)person);break;case TENANT:tenantList.push_back((Tenant*)person);break;default:printf("wrong person\n");}}void operation(Colleague* person){switch (person->getPersonType()){case LANDLORD:for (int i = 0; i < tenantList.size(); i++){tenantList[i]->answer();}break;case TENANT:for (int i = 0; i < landlordList.size(); i++){landlordList[i]->answer();}break;default:break;}}
private:vector<Landlord*>landlordList;vector<Tenant*>tenantList;
};

4.3.3 同事类

抽象同事类

// 前向声明
class Mediator;
class Agency;// 抽象同事类
class Colleague
{public:Colleague(){}void setMediator(Mediator* iMediator){this->mediator = iMediator;}Mediator* getMediator(){return this->mediator;}void setPersonType(PERSON_TYPE iPersonType){this->personType = iPersonType;}PERSON_TYPE getPersonType(){return this->personType;}virtual void ask() = 0;virtual void answer() = 0;
private:PERSON_TYPE personType;Mediator* mediator;
};

具体同事类——房东(Landlord)

// 具体同事类:房东
class Landlord :public Colleague
{public:Landlord();Landlord(string iName, int iPrice, string iAddress, string iPhoneNum);void ask();void answer();
private:string name;int price;string address;string phoneNumber;
};

实现:

#include "Colleague.h"
#include "Mediator.h"Landlord::Landlord(){name = "none";price = 0;address = "none";phoneNumber = "none";setPersonType(NONE_PERSON);
}Landlord::Landlord(string iName, int iPrice, string iAddress, string iPhoneNum){name = iName;price = iPrice;address = iAddress;phoneNumber = iPhoneNum;setPersonType(LANDLORD);
}void Landlord::answer(){printf("房东姓名:%s, 房租:%d, 地址:%s, 联系电话:%s\n",name.c_str(), price, address.c_str(), phoneNumber.c_str());
}void Landlord::ask(){printf("房东%s查看租客信息:\n",name.c_str());(this->getMediator())->operation(this);
}

4.3.4 具体同事类——租客(Tenant)

声明:

// 具体同事类:租客
class Tenant :public Colleague
{public:Tenant();Tenant(string name);void ask();void answer();
private:string name;
};

实现:

#include "Colleague.h"
#include "Mediator.h"Tenant::Tenant(){name = "none";setPersonType(NONE_PERSON);
}Tenant::Tenant(string iName){name = iName;setPersonType(TENANT);
}void Tenant::ask(){printf("租客%s询问房东信息\n", name.c_str()); (this->getMediator())->operation(this);
}void Tenant::answer(){printf("租客姓名:%s\n", name.c_str());
}

4.3.5 客户端代码示例及效果

#include <iostream>
#include "Mediator.h"
#include "Colleague.h"int main()
{// 创建租房中介Agency *mediator = new Agency();// 创建3位房东Landlord *fangdong1 = new Landlord("刘备", 1350, "1区", "111");Landlord *fangdong2 = new Landlord("关羽", 1500, "2区", "222");Landlord *fangdong3 = new Landlord("张飞", 1000, "3区", "333");fangdong1->setMediator(mediator);fangdong2->setMediator(mediator);fangdong3->setMediator(mediator);// 房东在中介处登记注册房源信息mediator->registerMethod(fangdong1);mediator->registerMethod(fangdong2);mediator->registerMethod(fangdong3);// 创建两位租客Jungle和贱萌兔Tenant *jungle = new Tenant("Jungle");Tenant *jianmengtu = new Tenant("贱萌兔");jungle->setMediator(mediator);jianmengtu->setMediator(mediator);// Jungle和贱萌兔在中介处登记求租信息mediator->registerMethod(jungle);mediator->registerMethod(jianmengtu);jungle->ask();printf("\n\n");fangdong1->ask();printf("\n\n");system("pause");return 0;
}

4.3.6 运行结果

4.4 总结

优点:

  • 简化了对象之间的交互,通过中介者,对象之间的多对多关系就简化了相对更简单的一对多关系;
  • 可将各个同事对象解耦,利于各个同事之间的松散耦合,可独立地改变和复用每一个同事对象,增加新的中介者和同事都比较方便,符合开闭原则;
  • 可减少子类生成,将原本分布于多个对象之间的行为封装在一起,只需生成新的具体中介者类就可以改变这些行为。

缺点:

  • 具体中介者类中包含了大量与同事之间交互的细节和逻辑,可能使得中介者类很复杂以至于难以管理维护。

适用环境:

  • 系统中的对象之间存在复杂的交互关系,使得系统内逻辑错综复杂,难以管理;
  • 一个对象引用了其他很多对象,并直接和这些对象交互,导致该对象难以复用。

五、备忘录模式

“Ctrl+Z”是什么操作?各位都用过,并且经常使用吧?撤销!撤销上一个操作返回上一个状态,甚至撤销好几个操作,返回到几个操作之前的状态。这个操作非常有用,一旦我们某一步操作失误,可以选择撤销操作来返回原来的无错状态。

那么系统怎么知道每一步的状态呢?它一定保存了一定数量的历史状态!就像Git版本控制一样,保存着每一次提交的状态,使用者可以随时reset到历史某个状态,就像一个备忘录一样,保存了某些阶段的状态。

5.1 备忘录模式简介

类似于上述引言的例子,在软件系统的操作过程中,难免会出现一些不当的操作,使得系统状态出现某些故障。如果能够有一种机制——能够保存系统每个阶段的状态,当用户操作失误的时候,可以撤销不当的操作,回到历史某个阶段——那么软件系统将更加灵活和人性化。

有没有这样的一种解决方案呢?有!那就是备忘录模式。备忘录模式提供了一种状态恢复的机制,用户可以方便地回到指定的某个历史状态。很多软件的撤销操作,就使用了备忘录模式。

备忘录模式:

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

5.2 备忘录模式结构

备忘录模式的UML图如下所示:

备忘录模式主要有以下角色:

  • Originator(原发器):通过创建一个备忘录类存储当前的内部状态,也可以使用备忘录来恢复其内部状态,通常是将系统中需要保存内部状态的类设计为原发器;
  • Memento(备忘录):用于存储原发器的内部状态。备忘录的设计可以参考原发器的设计,根据需要确定备忘录类中的属性;除了原发器类对象,不允许其他对象修改备忘录。
  • Caretaker(负责人):负责保存备忘录,可以存储一个或多个备忘录对象,但是负责人只负责保存对象,不能修改对象,也不必知道对象的实现细节。(看好了,负责人可以存储多个备忘录对象,想一想这有什么用?是不是可以保存多个历史状态?实现多步撤销操作了)

备忘录模式的关键是备忘录类和负责人类的设计,以下是上述三个角色的典型实现:

#ifndef __DEMO_H__
#define __DEMO_H__// 前向声明
class Memento;// 原发器  典型实现
class Originator
{public:Originator(){state = "";}Originator(String iState){state = iState;}// 创建备忘录对象Memento* createMemento(){return new Memento(this);}// 利用备忘录对象恢复原发器状态void restoreMemento(Memento* m){state = m->getState();}void setState(string iState){ state = iState; }string getState(){ return state; }
private:string state;
};// 备忘录  典型实现(仿照原生器的设计)
class Memento
{public:Memento(){state = "";}Memento(Originator* o){state = o->getState();}void setState(String iState){state = iState;}string getState(){return state;}
private:String state;
};// 负责人  典型实现
class Caretaker
{public:Caretaker(){}Memento* getMemento(){return memento;}void setMemento(Memento *m){memento = m;}
private:Memento* memento;
};// 客户端 示例代码
int main()
{// 创建原发器对象Originator o = new Originator("状态1");// 创建负责人对象Caretaker *c = new Caretaker();c->setMemento(o->createMemento());o->setState("状态2");// 从负责人对象中取出备忘录对象,实现撤销o->restoreMemento(c->getMemento());return 0;
}#endif

5.3 备忘录模式代码实例

Jungle正在为代码版本管理苦恼,有时候为了尝试某个功能就去修改代码,导致原有的健壮的代码被破坏。所以Jungle希望能够设计一个代码保存和版本回退功能的demo,方便代码的管理。

本实例中,原生器为CodeVersion,具有版本号version、提交日期date和标签label三个状态需要备忘录Memento保存;管理者是CodeManager,具有提交代码commit(即保存一个版本)、回退到指定版本switchToPointedVersion(即撤销操作)和查看提交历史codeLog的功能。该实例的UML图如下图,具体代码如下。

5.3.1 备忘录Memento

#ifndef __MEMENTO_H__
#define __MEMENTO_H__class Memento
{public:Memento(){}Memento(int iVersion, string iDate, string iLabel){version = iVersion;date = iDate;label = iLabel;}void setVersion(int iVersion){version = iVersion;}int getVersion(){return version;}void setLabel(string iLabel){label = iLabel;}string getLabel(){return label;}void setDate(string iDate){date = iDate;}string getDate(){return date;}
private:int version;string date;string label;
};#endif

5.3.2 原生器CodeVersion

#ifndef __CODEVERSION_H__
#define __CODEVERSION_H__#include <iostream>
using namespace std;#include "Memento.h"// 原生器:CodeVersion
class CodeVersion
{public:CodeVersion(){version = 0;date = "1900-01-01";label = "none";}CodeVersion(int iVersion, string iDate, string iLabel){version = iVersion;date = iDate;label = iLabel;}// 保存代码Memento* save(){return new Memento(this->version, this->date, this->label);}// 回退版本void restore(Memento* memento){setVersion(memento->getVersion());setDate(memento->getDate());setLabel(memento->getLabel());}void setVersion(int iVersion){version = iVersion;}int getVersion(){return version;}void setLabel(string iLabel){label = iLabel;}string getLabel(){return label;}void setDate(string iDate){date = iDate;}string getDate(){return date;}
private:// 代码版本int version;// 代码提交日期string date;// 代码标签string label;
};#endif

5.3.3 管理者CodeManager

#ifndef __CODEMANAGER_H__
#define __CODEMANAGER_H__#include "Memento.h"
#include <vector>
using namespace std;// 管理者
class CodeManager
{public:CodeManager(){}void commit(Memento* m){printf("提交:版本-%d, 日期-%s, 标签-%s\n", m->getVersion(), m->getDate().c_str(), m->getLabel().c_str());mementoList.push_back(m);}// 切换到指定的版本,即回退到指定版本Memento* switchToPointedVersion(int index){mementoList.erase(mementoList.begin() + mementoList.size() - index, mementoList.end());return mementoList[mementoList.size() - 1];}// 打印历史版本void codeLog(){for (int i = 0; i < mementoList.size(); i++){printf("[%d]:版本-%d, 日期-%s, 标签-%s\n", i, mementoList[i]->getVersion(),mementoList[i]->getDate().c_str(), mementoList[i]->getLabel().c_str());}}
private:vector<Memento*> mementoList;
};#endif

5.3.4 客户端代码示例及效果

#include "Originator.h"
#include "Memento.h"
#include "CodeManager.h"int main()
{CodeManager *Jungle = new CodeManager();CodeVersion* codeVer = new CodeVersion(1001, "2019-11-03", "Initial version");// 提交初始版本printf("提交初始版本:\n");Jungle->commit(codeVer->save());// 修改一个版本,增加了日志功能printf("\n提交一个版本,增加了日志功能:\n");codeVer->setVersion(1002);codeVer->setDate("2019-11-04");codeVer->setLabel("Add log funciton");Jungle->commit(codeVer->save());// 修改一个版本,增加了Qt图片浏览器printf("\n提交一个版本,增加了Qt图片浏览器:\n");codeVer->setVersion(1003);codeVer->setDate("2019-11-05");codeVer->setLabel("Add Qt Image Browser");Jungle->commit(codeVer->save());// 查看提交历史printf("\n查看提交历史\n");Jungle->codeLog();// 回退到上一个版本printf("\n回退到上一个版本\n");codeVer->restore(Jungle->switchToPointedVersion(1));// 查看提交历史printf("\n查看提交历史\n");Jungle->codeLog();printf("\n\n");system("pause");return 0;
}

5.3.5 运行结果

5.4 总结

优点:

  • 实现状态恢复、撤销操作的功能,用户可以恢复到指定的历史状态,让软件系统更加人性化;
  • 备忘录封装了信息,除了原生器以外,其他对象访问不了备忘录的代码;

缺点:

  • 资源消耗大。如果需要保存原生器对象的多个历史状态,那么将创建多个备忘录对象;或者如果原生器对象的很多状态都需要保存,也将消耗大量存储资源。

适用环境:

  • 保存一个对象的历史状态,系统需要设计回退或者撤销功能;
  • 备忘录类可以封装一个对象的历史状态,避免对象的历史状态被外界修改。

六、观察者模式

一个对象行为的改变,其相关联的对象都会得到通知,并自动产生对应的行为。这在软件设计模式中,即是观察者模式。

6.1 观察者模式简介

软件系统中的对象并不是孤立存在的,一个对象行为的改变可能会引起其他所关联的对象的状态或行为也发生改变,即“牵一发而动全身”。观察者模式建立了一种一对多的联动,一个对象改变时将自动通知其他对象,其他对象将作出反应。观察者模式中,发生改变的对象称为“观察目标”,被通知的对象称为“观察者”。一个观察目标可以有很多个观察者。

观察者模式定义如下:

观察者模式:

定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。

观察者模式又被称为发布-订阅模式(Publish-Subscribe)、模型-视图模式(Model-View)、源-监听器模式(Source-Listener)、从属者模式(Dependents)。

6.2 观察者模式结构

观察者模式由观察者和观察目标组成,为便于扩展,两个角色都设计了抽象层。观察者模式的UML图如下:

  • Subject(目标):是被观察的对象,目标中定义了一个观察者的集合,即一个目标可能会有多个观察者,通过attach()和detach()方法来增删观察者对象。目标声明了通知方法notify(),用于在自身状态发生改变时通知观察者。
  • ConcreteSubject(具体目标):具体目标实现了通知方法notify(),同时具体目标有记录自身状态的属性和成员方法;
  • Observer(观察者):观察者将对接收到的目标发生改变的通知做出自身的反应,抽象层声明了更新方法update();
  • ConcreteObserver(具体观察者): 实现了更新方法update(),具体观察者中维护了一个具体目标对象的引用(指针),用于存储目标的状态。

下述是观察者模式的典型实现:

#ifndef __DEMO_H__
#define __DEMO_H__// 抽象观察者
class Observer
{public:// 声明响应更新方法virtual void update() = 0;
};// 具体观察者
class ConcreteObserver:public Observer
{public:// 实现响应更新方法void update(){// 具体操作}
};// 抽象目标
class Subject
{public:// 添加观察者void attach(Observer* obs){obsList.push_back(obs);}// 移除观察者void detach(Observer* obs){obsList.remove(obs);}// 声明通知方法virtual void notify() = 0;
protected:// 观察者列表list<Observer*>obsList;
};// 具体目标
class ConcreteSubject :public Subject
{public:// 实现通知方法void notify(){// 具体操作// 遍历通知观察者对象for (int i = 0; i < obsList.size(); i++){obsList[i]->update();}}
};// 客户端代码示例
int main()
{Subject  *sub = new ConcreteSubject();Observer *obs = new ConcreteObserver();sub->attach(obs);sub->notify();return 0;
}
#endif //__DEMO_H__

6.3 观察者模式代码实例

玩过和平精英这款游戏吗?四人组队绝地求生,当一个队友发现物资时,可以发消息“我这里有物资”,其余三个队友听到后可以去取物资;当一个队友遇到危险时,也可以发消息“救救我”,其余三个队友得到消息后便立马赶去营救。本例Jungle将用观察者模式来模拟这个过程。

本例的UML图如下:

本例中,抽象观察者是Observer,声明了发现物资或者需要求救时的呼叫的方法call(),具体观察者是Player,即玩家,Player实现了呼叫call()方法,并且还定义了取物资come()和支援队友help()的方法。本例定义了AllyCenter作为抽象目标,它维护了一个玩家列表playerList,并且定义了加入战队和剔除玩家的方法。具体目标是联盟中心控制器AllyCenterController,它实现了通知notify()方法,该方法将队友call的消息传达给玩家列表里的其余队友,并作出相应的响应。

6.3.1 公共头文件

通过一个枚举类型来定义两种消息类型,即发现物资和求助

#ifndef __COMMON_H__
#define __COMMON_H__enum INFO_TYPE{NONE,RESOURCE,HELP
};#endif //__COMMON_H__

6.3.2 观察者

抽象观察者Observer

// 抽象观察者 Observer
class Observer
{public:Observer(){}// 声明抽象方法virtual void call(INFO_TYPE infoType, AllyCenter* ac) = 0;string getName(){return name;}void setName(string iName){this->name = iName;}
private:string name;
};

具体观察者Player

// 具体观察者
class Player :public Observer
{public:Player(){setName("none");}Player(string iName){setName(iName);}// 实现void call(INFO_TYPE infoType, AllyCenter* ac){switch (infoType){case RESOURCE:printf("%s :我这里有物资\n", getName().c_str());break;case HELP:printf("%s :救救我\n", getName().c_str());break;default:printf("Nothing\n");}ac->notify(infoType, getName());}// 实现具体方法void help(){printf("%s:坚持住,我来救你!\n", getName().c_str());}void come(){printf("%s:好的,我来取物资\n", getName().c_str());}
};

6.3.3 目标类

抽象目标AllyCenter
声明

// 抽象目标:联盟中心
class AllyCenter
{public:AllyCenter();// 声明通知方法virtual void notify(INFO_TYPE infoType, std::string name) = 0;// 加入玩家void join(Observer* player);// 移除玩家void remove(Observer* player);
protected:// 玩家列表std::vector<Observer*>playerList;
};

实现

#include "AllyCenter.h"
#include "Observer.h"AllyCenter::AllyCenter(){printf("大吉大利,今晚吃鸡!\n");
}// 加入玩家
void AllyCenter::join(Observer* player){if (playerList.size() == 4){printf("玩家已满\n");return;}printf("玩家 %s 加入\n", player->getName().c_str());playerList.push_back(player);if (playerList.size() == 4){printf("组队成功,不要怂,一起上!\n");}
}
// 移除玩家
void AllyCenter::remove(Observer* player){printf("玩家 %s 退出\n", player->getName().c_str());//playerList.remove(player);
}

具体目标AllyCenterController
声明:

// 具体目标
class AllyCenterController :public AllyCenter
{public:AllyCenterController();// 实现通知方法void notify(INFO_TYPE infoType, std::string name);
};

实现:

AllyCenterController::AllyCenterController(){}// 实现通知方法
void AllyCenterController::notify(INFO_TYPE infoType, std::string name){switch (infoType){case RESOURCE:for each (Observer* obs in playerList){if (obs->getName() != name){((Player*)obs)->come();}}break;case HELP:for each (Observer* obs in playerList){if (obs->getName() != name){((Player*)obs)->help();}}break;default:printf("Nothing\n");}
}

6.3.4 客户端代码示例及效果

#include "Observer.h"
#include "AllyCenter.h"int main()
{// 创建一个战队AllyCenterController* controller = new AllyCenterController();// 创建4个玩家,并加入战队Player* Jungle = new Player("Jungle");Player* Single = new Player("Single");Player* Jianmengtu = new Player("贱萌兔");Player* SillyDog = new Player("傻子狗");controller->join(Jungle);controller->join(Single);controller->join(Jianmengtu);controller->join(SillyDog);printf("\n\n");// Jungle发现物资,呼叫队友Jungle->call(RESOURCE, controller);printf("\n\n");// 傻子狗遇到危险,求救队友SillyDog->call(HELP, controller);printf("\n\n");system("pause");return 0;
}

6.3.5 运行结果

6.4 观察者模式的应用

观察者模式是一种使用频率非常高的设计模式,几乎无处不在。凡是涉及一对一、一对多的对象交互场景,都可以使用观察者会模式。比如购物车,浏览商品时,往购物车里添加一件商品,会引起UI多方面的变化(购物车里商品数量、对应商铺的显示、价格的显示等);各种编程语言的GUI事件处理的实现;所有的浏览器事件(mouseover,keypress等)都是使用观察者模式的例子。

6.5 总结

优点:

  • 观察者模式实现了稳定的消息更新和传递的机制,通过引入抽象层可以扩展不同的具体观察者角色;
  • 支持广播通信,所有已注册的观察者(添加到目标列表中的对象)都会得到消息更新的通知,简化了一对多设计的难度;
  • 符合开闭原则,增加新的观察者无需修改已有代码,在具体观察者与观察目标之间不存在关联关系的情况下增加新的观察目标也很方便。

缺点:

  • 代码中观察者和观察目标相互引用,存在循环依赖,观察目标会触发二者循环调用,有引起系统崩溃的风险;
  • 如果一个观察目标对象有很多直接和简介观察者,将所有的观察者都通知到会耗费大量时间。

适用环境:

  • 一个对象的改变会引起其他对象的联动改变,但并不知道是哪些对象会产生改变以及产生什么样的改变;
  • 如果需要设计一个链式触发的系统,可是使用观察者模式;
  • 广播通信、消息更新通知等场景。

七、状态模式

“人有悲欢离合,月有阴晴圆缺”。很多事物在特定条件下转换成不同的状态,在不同状态下表现出不同的行为。

在软件系统中,有些对象在不同的条件下也具有不同的状态,不同状态之间可以相互转换。通过判断不同的条件分支(if…else…或者switch…case…)可以进行状态的转换。但这样势必使得代码的判断逻辑变得复杂,降低系统的可维护性。如果新加入一种状态,还需要修改判断逻辑,不符合开闭原则。

为解决复杂对象的多种状态转换问题,并使客户端代码与对象状态之间的耦合度降低,可以使用状态模式。

7.1 状态模式简介

状态模式将一个对象的状态从对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化。对于客户端而言,无需关心对象转态的转换以及对象所处的当前状态,无论处于何种状态的对象,客户端都可以一致处理。

状态模式: 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

7.2 状态模式结构

状态模式的UML图如下。

状态模式引入了抽象层,具有抽象状态类和具体状态类,还包括一个上下文境类:

  • Context(上下文类):是拥有多种状态的对象。上下文类的状态存在多样性,并且在不同的状态下,对象表现出不同的行为。在上下文类中,维护了一个抽象状态类的实例。
  • State(抽象状态类):声明了一个接口,用于封装与在上下文类中的一个特定状态相关的行为,在子类中实现在各种不同状态对应的方法。不同的子类可能存在不同的实现方法,相同的方法可以写在抽象状态类中。
  • ConcreteState(具体状态类):实现具体状态下的方法,每一个具体状态类对应一个具体的状态。

值得注意的是,上下文中维护了一个状态类的指针或者引用,可以由上下文类来觉得具体实例化为哪一个具体的状态对象,也可以由具体的状态类来决定转换为哪一个实例,所以,上下文类和状态类之间存在依赖甚至相互引用的关系:

// 1.由环境类来决定实例化为哪一个具体状态类对象
class Context
{public:void convertState(){if (condition1){this->state = new ConcreteStateA();}else if (condition2){this->state = new ConcreteStateB();}else{// do something}}
private:// 引用状态对象State *state;
};
// 2.有具体状态类来决定转换成哪一个具体状态类对象
class ConcreteState :public State
{public:void convertState(Context* ctx){if (condition1){ctx->setState(new ConcreteStateA());}else if (condition2){ctx->setState(new ConcreteStateB());}else{// do something}}
};

下面是状态模式的典型用法:

#ifndef __DEMO_H__
#define __DEMO_H__// 抽象状态类
class State
{public:// 声明抽象方法virtual void handle() = 0;
};// 具体状态类
class ConcreteState :public State
{public:// 实现void handle(){// ……}
};// 上下文类
class Context
{public:// set方法设置状态对象void setState(State* iState){this->state = iState;}// 对外封装的方法void request(){// do somethingstate->handle();}
private:// 引用状态对象State *state;
};#endif //__DEMO_H__

7.3 状态模式代码实例

接下来Jungle用一个实例来应用状态模式。

在某纸牌游戏中,游戏人物分为入门级(primary)、熟练级(Secondary)、高手级(Professional)和骨灰级(Final)四种级别,由人物的积分来划分角色等级,游戏胜利将增加积分,失败将扣除积分。入门级有最基本的游戏功能play(),熟练级增加了游戏胜利积分加倍功能doubleScore(),高手级在熟练级的基础上增加了换牌功能changeCards(),骨灰级在高手级的基础上再增加了偷看他人纸牌的功能peekCards()。

积分规则如下:

基础分:100,游戏胜利+50分,游戏失败+30分;

入门级:0150;熟练级150200;高手级:200~250;骨灰级:250以上

本例设计游戏账户GameAccount为上下文类,维护了一个级别类(Level)的对象实例。GameAccount中定义了一个代表积分的score整型和统一封装的方法playcard(),在该方法中再调用具体级别的各个技能方法。采用随机数的方式来随机判定牌局的输赢,以增减积分。

级别类Level为抽象类,声明了play()、doubleScore()、changeCards()、seekCards()的抽象方法,在四个具体级别类Primary、Secondary、Professional和Final类中具体实现了该方法,具体来说是根据该级别是否有权利使用该技能来打印一行话。upgradeLevel()方法用于判断每局牌结束后该游戏账户的积分是否可以升级或者降级,通过setLevel()方法改变当前账户的游戏级别。

7.3.1 上下文类:游戏账户类

//头文件
#ifndef __GAMEACCOUNT_H__
#define __GAMEACCOUNT_H__using namespace std;
#include <iostream>
// 前向声明
class Level;class GameAccount
{public:GameAccount();GameAccount(string iName);string getName();void win();void lose();void playCard();void setLevel(Level*);int getScore();void setScore(int);private:Level* level;int score;string name;
};#endif//源文件
#include "GameAccount.h"
#include "Level.h"
#include <Windows.h>
#include <time.h>
#define  random(x) (rand()%x)GameAccount::GameAccount(){printf("创立游戏角色,积分:100,级别:PRIMARY\n");score = 100;name = "none";setLevel(new Primary(this));
}GameAccount::GameAccount(string iName){printf("创立游戏角色,积分:100,级别:PRIMARY\n");score = 100;name = iName;setLevel(new Primary(this));
}void GameAccount::setLevel(Level* iLevel){this->level = iLevel;
}string GameAccount::getName(){return name;
}void GameAccount::playCard(){this->level->playCard();Sleep(100);srand((int)time(0));int res = random(2);if (res % 2 == 0){this->win();}else{this->lose();}this->level->upgradeLevel();
}void GameAccount::win(){if (this->getScore() < 200){setScore(getScore() + 50);}else{setScore(getScore() + 100);}printf("\n\t胜利,最新积分为 %d\n", score);
}void GameAccount::lose(){setScore(getScore() + 30);printf("\n\t输牌,最新积分为 %d\n", score);
}int GameAccount::getScore(){return this->score;
}void GameAccount::setScore(int iScore){this->score = iScore;
}

7.3.2 状态类

抽象状态类:Level
头文件:

#include "GameAccount.h"class Level
{public :Level();// 声明方法void playCard();void play();virtual void doubleScore() = 0;virtual void changeCards() = 0;virtual void peekCards() = 0;// 升级virtual void upgradeLevel() = 0;GameAccount* getGameAccount();void setGameAccount(GameAccount* iGameAccount);
private:GameAccount* gameAccount;
};

源文件:

Level::Level(){}void Level::playCard(){this->play();this->doubleScore();this->changeCards();this->peekCards();
}void Level::play(){printf("\t使用基本技能,");
}void Level::setGameAccount(GameAccount* iGameAccount){this->gameAccount = iGameAccount;
}GameAccount* Level::getGameAccount(){return gameAccount;
}

7.3.3 具体状态类:Primary

头文件:

class Primary :public Level
{public:Primary();Primary(Level* level);Primary(GameAccount* ga);void doubleScore();void changeCards();void peekCards();// 升级void upgradeLevel();
};

源文件:

Primary::Primary(){}Primary::Primary(GameAccount* iGameAccount){this->setGameAccount(iGameAccount);
}Primary::Primary(Level* level){getGameAccount()->setLevel(level);
}void Primary::doubleScore(){return;
}void Primary::changeCards(){return;
}void Primary::peekCards(){return;
}void Primary::upgradeLevel(){if (this->getGameAccount()->getScore() > 150){this->getGameAccount()->setLevel(new Secondary(this));printf("\t升级! 级别:SECONDARY\n\n");}else{printf("\n");}
}

7.3.4 具体状态类:Secondary

头文件:

class Secondary :public Level
{public:Secondary();Secondary(Level* level);void doubleScore();void changeCards();void peekCards();// 升级void upgradeLevel();
};

源文件:

Secondary::Secondary(){}Secondary::Secondary(Level* level){this->setGameAccount(level->getGameAccount());getGameAccount()->setLevel(level);
}void Secondary::doubleScore(){printf("使用胜利双倍积分技能");
}void Secondary::changeCards(){return;
}void Secondary::peekCards(){return;
}void Secondary::upgradeLevel(){if (this->getGameAccount()->getScore() < 150){this->getGameAccount()->setLevel(new Primary(this));printf("\t降级! 级别:PRIMARY\n\n");}else if (this->getGameAccount()->getScore() > 200){this->getGameAccount()->setLevel(new Professional(this));printf("\t升级! 级别:PROFESSIONAL\n\n");}
}

7.3.5 具体状态类:Professional

头文件:

class Professional :public Level
{public:Professional();Professional(Level* level);void doubleScore();void changeCards();void peekCards();// 升级void upgradeLevel();
};

源文件:

Professional::Professional(){}Professional::Professional(Level* level){this->setGameAccount(level->getGameAccount());getGameAccount()->setLevel(level);
}void Professional::doubleScore(){printf("使用胜利双倍积分技能,");
}void Professional::changeCards(){printf("使用换牌技能");
}void Professional::peekCards(){return;
}void Professional::upgradeLevel(){if (this->getGameAccount()->getScore() < 200){this->getGameAccount()->setLevel(new Secondary(this));printf("\t降级! 级别:SECONDARY\n\n");}else if (this->getGameAccount()->getScore() > 250){this->getGameAccount()->setLevel(new Final(this));printf("\t升级! 级别:FINAL\n\n");}
}

7.3.6 具体状态类:Final

头文件:

class Final :public Level
{public:Final();Final(Level* level);void doubleScore();void changeCards();void peekCards();// 升级void upgradeLevel();
};

源文件:

Final::Final(){}Final::Final(Level* level){this->setGameAccount(level->getGameAccount());getGameAccount()->setLevel(level);
}void Final::doubleScore(){printf("使用胜利双倍积分技能,");
}void Final::changeCards(){printf("使用换牌技能,");
}void Final::peekCards(){printf("使用偷看卡牌技能");
}void Final::upgradeLevel(){if (this->getGameAccount()->getScore() < 250){this->getGameAccount()->setLevel(new Professional(this));printf("\t降级! 级别:PROFESSIONAL\n\n");}else{printf("\t%s 已经是最高级\n\n", this->getGameAccount()->getName().c_str());}
}

7.3.7 客户端代码示例及结果

客户端代码创建了一个游戏账户Jungle,初始积分为100分,级别为Primary,即入门级,Jungle一共玩了5局牌。

#include "GameAccount.h"
#include "Level.h"int main()
{GameAccount *jungle = new GameAccount("Jungle");for (int i = 0; i < 5; i++){printf("第%d局:\n", i + 1);jungle->playCard();}printf("\n\n");system("pause");return 0;
}

7.3.8 运行结果

上面的代码不管Jungle当前是什么级别,都统一地调用了上下文类封装好的方法playcard(),即外界并不知道不同级别内部的具体实现细节。运行结果显示,Jungle的在不同的状态(级别)下能够表现不同的行为(不同的技能),并且能够不断改变自身的状态(升级或降级)。

7.4 总结

优点:

  • 状态模式封装了状态转换的规则,只给外界暴露了统一的接口,客户端可以无差别地调用该接口(如上述实例的客户端代码)
  • 状态模式将所有与具体状态有关的行为放到一个类(具体状态类)中,只需要注入(依赖)不同的状态类对象到上下文类中,即可使上下文中拥有不同的行为

缺点:

  • 状态模式增加了系统中类的个数(不同的具体状态类)
  • 结构相对复杂(如前述实例的UML图),代码逻辑也较复杂
  • 如果要增加新的状态,需要修改负责状态转换的代码,不符合开闭原则(如上述实例,如果增加了一个中间级别,是不是得修改很多状态转换的逻辑?)

适用环境:

  • 对象的行为根据它的状态的改变而不同
  • 代码中含有大量与对象状态有关的判断逻辑(if……else……或switch……case……)

八、策略模式

同样是排序算法,你可以选择冒泡排序、选择排序、插入排序、快速排序等等,也即是说,为了实现排序这一个目的,有很多种算法可以选择。这些不同的排序算法构成了一个算法族,你可以在需要的时候,根据需求或者条件限制(内存、复杂度等)适时选择具体的算法。

在面向对象的设计里,该如何设计这样一个算法族呢?它包含了多种算法,在使用的时候又会根据条件来选择具体的算法?这就会用到软件设计模式中的——策略模式。

8.1 策略模式简介

策略模式用于算法的自由切换和扩展,对应于解决某一问题的一个算法族,允许用户从该算法族中任意选择一个算法解决问题,同时还可以方便地更换算法或者增加新的算法。策略模式将算法族中的每一个算法都封装成一个类,每一个类称为一个策略(Strategy)。

策略模式:

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

8.2 策略模式结构

为了方便算法族中的不同算法在使用中具有一致性,在策略模式中会提供一个抽象层来声明公共接口,在具体的策略类中实现各个算法。策略模式由上下文类和策略类组成,其UML结构如下图:

  • Context(上下文类) :上下文类是使用算法的角色,可以在解决不同具体的问题时实例化不同的具体策略类对象;
  • Strategy(抽象策略类):声明算法的方法,抽象层的设计使上下文类可以无差别的调用不同的具体策略的方法;
  • ConcreteStrategy(具体策略类):实现具体的算法。

8.3 策略模式代码实例

某系统提供了一个用于对数组进行操作的类,该类封装了对数组的常见操作,现以排序操作为例,使用策略模式设计该数组操作类,使得客户端可以动态更换排序算法,可以根据需要选择冒泡排序或者选择排序或者插入排序,也能够灵活增加新的排序算法 。

显然,在该实例中,可以冒泡排序、选择排序和插入排序分别封装为3个具体策略类,它们有共同的基类SortStrategy。还需要一个上下文类Context,Context中维护了一个SortStrategy的指针,在客户端需要的时候,通过Context的setSortStrategy()方法来实例化具体的排序类对象。该实例的UML结构图如下:

8.3.1 排序策略类

抽象排序策略类

// 抽象策略类
class Strategy
{public:Strategy(){}virtual void sort(int arr[], int N) = 0;
};

具体策略类:冒泡排序类

// 具体策略:冒泡排序
class BubbleSort :public Strategy
{public:BubbleSort(){printf("冒泡排序\n");}void sort(int arr[], int N){for (int i = 0; i<N; i++){for (int j = 0; j<N - i - 1; j++){if (arr[j]>arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}}
};

具体策略类:插入排序类

// 具体策略:插入排序
class InsertSort :public Strategy
{public:InsertSort(){printf("插入排序\n");}void sort(int arr[], int N){int i, j;for (i = 1; i<N; i++){for (j = i - 1; j >= 0; j--){if (arr[i]>arr[j]){break;}}int temp = arr[i];for (int k = i - 1; k > j; k--){arr[k + 1] = arr[k];}arr[j + 1] = temp;}}
};

8.3.2 上下文类

#ifndef __CONTEXT_H__
#define __CONTEXT_H__#include "Strategy.h"
#include <stdio.h>// 上下文类
class Context
{public:Context(){arr = NULL;N = 0;}Context(int iArr[], int iN){this->arr = iArr;this->N = iN;}void setSortStrategy(Strategy* iSortStrategy){this->sortStrategy = iSortStrategy;}void sort(){this->sortStrategy->sort(arr, N);printf("输出: ");this->print();}void setInput(int iArr[], int iN){this->arr = iArr;this->N = iN;}void print(){for (int i = 0; i < N; i++){printf("%3d ", arr[i]);}printf("\n");}private:Strategy* sortStrategy;int* arr;int N;
};#endif // __CONTEXT_H__

8.3.3 客户端代码示例及结果

#include "Context.h"
#include <stdio.h>
#include <stdlib.h>int main()
{Context* ctx = new Context();int arr[] = { 10, 23, -1, 0, 300, 87, 28, 77, -32, 2 };ctx->setInput(arr, sizeof(arr)/sizeof(int));printf("输入:");ctx->print();// 冒泡排序ctx->setSortStrategy(new BubbleSort());ctx->sort();// 选择排序ctx->setSortStrategy(new SelectionSort());ctx->sort();// 插入排序ctx->setSortStrategy(new InsertSort());ctx->sort();printf("\n\n");system("pause");return 0;
}

8.3.4 运行结果

从客户端代码可以看到,客户端无需关心具体排序算法的细节,都是统一的调用上下文的sort()接口。另外,如果要增加新的排序算法,比如快速排序QuickSort,只需要从基类SortStrategy在派生一个类QuickSort,在QuickSort类中实现具体的sort()算法即可,扩展起来非常方便。

8.4 总结

优点:

  • 符合开闭原则,策略模式易于扩展,增加新的算法时只需继承抽象策略类,新设计实现一个具体策略类即可;
  • 客户端可以无差别地通过公共接口调用,利用里式替换原则,灵活使用不同的算法策略;
  • 提供了一个算法族管理机制和维护机制。

缺点:

  • 客户端必须要知道所有的策略,以便在使用时按需实例化具体策略;
  • 系统会产生很多单独的类,增加系统中类的数量;
  • 客户端在同一时间只能使用一种策略。

适用环境:

  • 系统需要在一个算法族中动态选择一种算法,可以将这些算法封装到多个具体算法类中,这些算法类都有共同的基类,即可以通过一个统一的接口调用任意一个算法,客户端可以使用任意一个算法;
  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装与算法相关的数据结构,可以提高算法的安全性。

九、模板方法模式

类的继承你一定用过,派生类覆写基类的方法你也一定用过,只是你可能不知道,这就是传说中的一种设计模式……

9.1 模板方法模式简介

模板方法模式是较简单且常用的一种设计模式,是基于类的继承的一种代码复用技术,其结构只存在基类和派生类之间的继承关系。模板方法是一个具体的方法,给出了一个顶层逻辑流程框架。

模板方法模式:

定义一个操作中的算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

9.2 模板方法结构

模板方法的结构很简单,只有基类和派生类两个角色:

AbstractClass(基类):即抽象类,在基类中定义或声明了一系列基本操作method,这些操作是具体或者抽象的,每一个操作都对应算法的一个步骤,在其派生类中可以重定义。基类中定义了一个模板方法(template method),它规定了算法的流程框架,模板方法由基类定义或声明的一系列基本操作按照一定流程实现。
ConcreteClass(派生类):实现在基类中声明的抽象方法,也可以覆盖在基类中已经实现的方法。
模板方法模式的UML图如下:

模板方法模式的典型代码如下:

#ifndef __DEMO_H__
#define __DEMO_H__// 抽象类(基类)
class AbstractClass
{public:// 模板方法,定义一个算法的框架流程void templateMethod(){// do somethingmethod1();method2();method3();}// 基本方法——公共方法void mehtod1(){// do something}// 基本方法2virtual void method2() = 0;// 基本方法3——默认实现void mehtod3(){// do something}
};// 具体类(派生类)
class ConcreteClass :public AbstractClass
{public:// 实现基本方法2void method2(){// do something}// 重定义基本方法3,覆盖基类的方法3void method3(){// do something}
};#endif

9.3 模板方法模式代码实例

某个指纹处理模块可以在两种模式下处理算法,即安全模式和非安全模式。在安全模式下,为了保证数据安全,某个指纹识别流程需要对采得的指纹图像进行加密,在处理图像之前再对加密数据进行解密。而非安全模式这不需要加密解密过程。指纹算法流程如下:采图——加密——解密——算法处理指纹——处理结果。现用模板方法模式模拟上述过程。

在这个实例中,Jungle首先定义了基类FingerprintModule,声明了基本方法:采图getImage()、判断是否在安全模式isSafeMode()、加密encrypt()、解密decrypt()、处理指纹图像processImage()、输出结果output(),在基类中定义了一个模板方法algorithm(),该方法里定义了指纹算法流程。

从基类FingerprintModule派生出3个子类,分别是FingerprintModuleA、FingerprintModuleB和FingerprintModuleC,三个子类的特点在于:

  • FingerprintModuleA:安全模式,采用RSA秘钥加解密,采用第一代版本算法处理指纹图像;
  • FingerprintModuleB:非安全模式,采用第二代版本算法处理指纹图像;
  • FingerprintModuleC:安全模式,采用DH秘钥加解密,采用第一代版本算法处理指纹图像;

该实例的UML图如下:

9.3.1 基类

// 基类
class FingerprintModule
{public:FingerprintModule(){}void getImage(){printf("采指纹图像\n");}void output(){printf("指纹图像处理完成!\n");}virtual bool isSafeMode() = 0;virtual void processImage() = 0;// 加解密virtual void encrypt() = 0;virtual void decrypt() = 0;// 模板方法void algorithm(){// 1.采图getImage();// 2.安全模式下加密和解密if (isSafeMode()){// 2.1. 加密encrypt();// 2.2. 解密decrypt();}// 3.处理ImageprocessImage();// 4.处理结果output();}
};

9.3.2 派生类

// 派生类
class FingerprintModuleA :public FingerprintModule
{public:FingerprintModuleA(){}void processImage(){printf("使用 第一代版本算法 处理指纹图像\n");}bool isSafeMode(){printf("安全模式\n");return true;}void encrypt(){printf("使用RSA密钥加密\n");}void decrypt(){printf("使用RSA密钥解密\n");}
};// 派生类
class FingerprintModuleB :public FingerprintModule
{public:FingerprintModuleB(){}void processImage(){printf("使用 第二代版本算法 处理指纹图像\n");}bool isSafeMode(){printf("非安全模式\n");return false;}void encrypt(){}void decrypt(){}
};// 派生类
class FingerprintModuleC :public FingerprintModule
{public:FingerprintModuleC(){}void processImage(){printf("使用 第一代版本算法 处理指纹图像\n");}bool isSafeMode(){printf("安全模式\n");return true;}void encrypt(){printf("使用DH密钥加密\n");}void decrypt(){printf("使用DH密钥解密\n");}
};

9.3.3 客户端代码实例及效果

#include "FingerprintModule.h"
#include <Windows.h>int main()
{FingerprintModule *fp = new FingerprintModuleA();fp->algorithm();fp = new FingerprintModuleB();fp->algorithm();fp = new FingerprintModuleC();fp->algorithm();printf("\n\n");system("pause");return 0;
}

9.3.4 运行结果

9.4 总结

模板方法模式是基于类的继承的一种设计模式,使用非常频繁,被广泛应用于框架设计。

优点:

  • 在基类中定义算法的框架,并声明一些流程方法,由具体派生类实现细节,派生类中的实现并不会影响基类定义的算法的框架流程;
  • 公共行为在基类中提供实现,有利于代码复用;
  • 派生类可以覆盖基类的方法,重新实现某些方法,具有灵活性;
  • 可以很方便的扩展和更换派生类而不影响基类和其他派生类,符合开闭原则和单一职责原则。

缺点:

  • 模板方法模式要为每一个不同的基本方法提供一个派生类,如果基类中基本方法很多,那系统中会定义很多个派生类,导致类的个数很多,系统更加庞大。

适用环境:

  • 分割复杂算法,可以将算法的框架流程定义在基类中,设计为模板方法;而具体的细节由派生类设计实现;
  • 各个派生类的公共部分提取到基类中,以实现代码复用;
  • 派生类需要覆盖基类的某些方法。

十、访问者模式

10.1 访问者模式简介

软件设计中也需要这样的类似于习题册的对象结构,不同的对象对应不同的处理。设计模式中,访问者模式就是为了以不同的方式来操作复杂的对象结构。

访问者模式是一种较为复杂的行为型设计模式,具有访问者和被访问元素两个主要的角色。被访问的元素常常有不同的类型,不同的访问者可以对它们提供不同的访问方式。被访问元素通常不是单独存在,而是以集合的形式存在于一个对象结构中,访问者可以遍历该对象结构,以逐个访问其中的每一个元素。

访问者模式:

表示一个作用于某对象结构中的各个元素的操作。访问者模式让用户可以在不改变各元素的前提下定义作用于这些元素的新操作。

10.2 访问者模式结构

访问者模式的结构相对较复杂,角色有如下几个:

  • Visitor(抽象访问者):抽象类,声明了访问对象结构中不同具体元素的方法,由方法名称可知该方法将访问对象结构中的某个具体元素;
  • ConcreteVisitor(具体访问者):访问某个具体元素的访问者,实现具体的访问方法;
  • Element(抽象元素):抽象类,一般声明一个accept()的方法,用于接受访问者的访问,accept()方法常常以一个抽象访问者的指针作为参数;
  • ConcreteElement(具体元素):针对具体被访问的元素,实现accept()方法;
  • ObjectStructure(对象结构):元素的集合,提供了遍历对象结构中所有元素的方法。对象结构存储了不同类型的元素对象,以供不同的访问者访问。

访问者模式的UML结构图如下:

从上图和前述可以看出,访问者模式中有两个层次结构:

  • 访问者的层次结构:抽象访问者和具体访问者,不同的具体访问者有不同的访问方式(visit()方式);
  • 被访问元素的层次结构:抽象元素和具体元素,不同的具体元素有不同的被访问方式(accept()方式)
    正式由于有这两个层次结构,在增加新的访问者时,不必修改已有的代码,通过继承抽象访问者即可实现扩展,符合开闭原则,系统扩展性较好。但是在增加新的元素时,既要修改抽象访问者类(增加访问新增元素方法的声明),又要修改具体访问者(增加新的具体访问者类),不符合开闭原则。
#ifndef __DEMO_H__
#define __DEMO_H__// 抽象访问者 Visitor
class Visitor
{public:virtual void visit(ConcreteElementA*) = 0;virtual void visit(ConcreteElementB*) = 0;
};// 具体访问者 ConcreteVisitor
class ConcreteVisitor :public Visitor
{public:// 实现一种针对特定元素的访问操作void visit(ConcreteElementA*){// 元素A的访问操作代码}void visit(ConcreteElementB*){// 元素B的访问操作代码}
};// 抽象元素
class Element
{public:// 声明抽象方法,以一个抽象访问者的指针作为函数参数virtual void accept(Visitor*) = 0;
};// 具体元素
class ConcreteElement :public Element
{public:void accept(Visitor* visitor){visitor->visit(this);}
};// 对象结构
class ObjectStructure
{public://  提供接口接受访问者访问void accept(Visitor* visitor){// 遍历访问对象结构中的元素for (){elementList[i]->accept(visitor);}}void addElement(){}void removeElement(){}
private:lsit<Element*>elementList;
};#endif

10.3 访问者模式代码实例

Jungle作为一名顾客,去超市购物,加入购物车的商品包括两种苹果和两本书,结账时收银员需要计算各个商品的的价格。本例Jungle采用访问者模式来模拟该过程。

本例中,客户Jungle和收银员都会去访问商品,但关心的地方不同:Jungle关心的是苹果和书的单价、品牌等,收银员关注的是商品的价格。因此,客户Customer和收银员Cashier是具体访问者,而苹果Apple和书Book是具体被访问元素;而购物车则是对象结构。本例的UML图如下:

10.3.1 元素类

抽象元素

// 抽象元素
class Element
{public:Element(){};virtual void accept(Visitor*) = 0;void setPrice(int iPrice){this->price = iPrice;}int getPrice(){return this->price;}void setNum(int iNum){this->num = iNum;}int getNum(){return num;}void setName(string iName){this->name = iName;}string getName(){return this->name;}
private:int price;int num;string name;
};

具体元素Apple

// 具体元素:Apple
class Apple :public Element
{public:Apple();Apple(string name, int price);void accept(Visitor*);
};

实现:

Apple::Apple(){setPrice(0);setNum(0);setName("");
}
Apple::Apple(string name, int price){setPrice(price);setNum(0);setName(name);
}void Apple::accept(Visitor* visitor){visitor->visit(this);
}

具体元素Book

// 具体元素:Book
class Book :public Element
{public:Book();Book(string name, int price);void accept(Visitor*);
};

实现:

Book::Book(){setPrice(0);setNum(0);setName("");
}Book::Book(string iName, int iPrice){setPrice(iPrice);setNum(0);setName(iName);
}void Book::accept(Visitor* visitor){visitor->visit(this);
}

10.3.2 访问者

抽象访问者

// 抽象访问者
class Visitor
{public:Visitor(){};// 声明一组访问方法virtual void visit(Apple*) = 0;virtual void visit(Book*) = 0;
};

具体访问者Customer

// 具体访问者:顾客
class Customer :public Visitor
{public:Customer();Customer(string iName);void setNum(Apple*, int);void setNum(Book*, int);void visit(Apple* apple);void visit(Book* book);
private:string name;
};

实现:

Customer::Customer(){this->name = "";
}Customer::Customer(string iName){this->name = iName;
}void Customer::setNum(Apple* apple, int iNum){apple->setNum(iNum);
}
void Customer::setNum(Book* book, int iNum){book->setNum(iNum);
}void Customer::visit(Apple* apple){int price = apple->getPrice();printf("  %s \t单价: \t%d 元/kg\n", apple->getName().c_str(), apple->getPrice());
}void Customer::visit(Book* book){int price = book->getPrice();string name = book->getName();printf("  《%s》\t单价: \t%d 元/本\n", book->getName().c_str(), book->getPrice());
}

具体访问者Cashier

class Cashier :public Visitor
{public:Cashier();void visit(Apple* apple);void visit(Book* book);
};

实现:

Cashier::Cashier(){}void Cashier::visit(Apple* apple){string name = apple->getName();int price = apple->getPrice();int num = apple->getNum();int total = price*num;printf("  %s 总价: %d 元\n", name.c_str(), total);
}void Cashier::visit(Book* book){int price = book->getPrice();string name = book->getName();int num = book->getNum();int total = price*num;printf("  《%s》 总价: %d 元\n", name.c_str(), total);
}

10.3.3 购物车ShoppingCart

class ShoppingCart
{public:ShoppingCart(){}void addElement(Element* element){printf("  商品名:%s, \t数量:%d, \t加入购物车成功!\n", element->getName().c_str(), element->getNum());elementList.push_back(element);}void accept(Visitor* visitor){for (int i = 0; i < elementList.size(); i++){elementList[i]->accept(visitor);}}
private:vector<Element*>elementList;
};

10.3.4 客户端代码示例及结果

#include "Element.h"
#include "Visitor.h"
#include "ShoppingCart.h"
#include <Windows.h>int main()
{Apple *apple1 = new Apple("红富士苹果", 7);Apple *apple2 = new Apple("花牛苹果", 5);Book *book1 = new Book("红楼梦", 129);Book *book2 = new Book("终结者", 49);Cashier* cashier = new Cashier();Customer* jungle = new Customer("Jungle");jungle->setNum(apple1, 2);jungle->setNum(apple2, 4);jungle->setNum(book1, 1);jungle->setNum(book2, 3);ShoppingCart* shoppingCart = new ShoppingCart();shoppingCart->addElement(apple1);shoppingCart->addElement(apple2);shoppingCart->addElement(book1);shoppingCart->addElement(book2);printf("\n\n");shoppingCart->accept(jungle);printf("\n\n");shoppingCart->accept(cashier);printf("\n\n");system("pause");return 0;
}

10.3.5 运行结果

10.4 总结

访问者模式的结构相对较复杂,在实际应用中使用频率较低。如果系统中存在一个复杂的对象结构,且不同的访问者对其具有不同的操作,那么可以考虑使用访问者模式。访问者模式的特点总结如下:

优点:

  • 增加新的访问者很方便,即增加一个新的具体访问者类,定义新的访问方式,无需修改原有代码,符合开闭原则;
  • 被访问元素集中在一个对象结构中,类的职责更清晰,利于对象结构中元素对象的复用;

缺点:

  • 增加新的元素类很困难,增加新的元素时,在抽象访问者类中需要增加一个对新增的元素方法的声明,即要修改抽象访问者代码;此外还要增加新的具体访问者以实现对新增元素的访问,不符合开闭原则;
  • 破坏了对象的封装性,访问者模式要求访问者对象访问并调用每一个元素对象的操作,那么元素对象必须暴露自己的内部操作和状态,否则访问者无法访问。

十一、解释器模式

11.1 解释器模式概述

解释器模式用于描述一个简单的语言解释器,主要应用于使用面向对象语言开发的解释器的设计。当需要开发一个新的语言是,可以使用解释器模式。

解释器模式:

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

解释器模式需要解决的是,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构件一个解释器,该解释器通过解释这些句子,来解决该问题。解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。

11.2 解释器模式的结构由抽象表达式、终结符表达式、非终结符表达式和环境类组成:

  • AbstractExpression(抽象表达式):声明了抽象的解释操作interpret(),是所有终结符表达式和非终结符表达式的基类;
    TerminalExpression(终结符表达式):终结符是文法规则的组成元素中最基本的语言单位,不能再分解。终结符表达式实现了与文法规则中终结符相关的解释操作,句子中的每一个终结符都是该类的一个实例。
  • NonterminalExpression(非终结符表达式):实现了文法规则中非终结符的解释操作,因为非终结符表达式同样可以包含终结符表达式,所以终结符表达式可以是非终结符表达式的成员。
  • Context(环境类):即上下文类,用于存储解释器之外的一些全局信息,通常临时存储需要解释的语句。

解释器模式的UML图如上所示。抽象表达式声明了抽象接口interpret(),终结符表达式和非终结符表达式式具体实现了该接口。其中,终结符表达式的interpret()接口实现了具体的解释操作,而非终结符表达式中可能包含终结符表达式或者非终结符表达式,所以非终结符表达式的interpret()接口中可能是递归调用每一个组成部分的interpret()方法。

11.3 解释器模式代码实例

设计一个简单的解释器,使得系统可以解释0和1的或运算和与运算(不考虑或运算和与运算的优先级,即从左往右依次运算),语句表达式和输出结果的几个实例如下表:

结合前面叙述的解释器模式的结构和本例,可以划分出以下角色:

  • 终结符表达式角色——值节点(ValueNode):0、1,因为它们是表达式的基本组成元素,不可再细分
  • 终结符表达式角色——运算符节点(OperatorNode):运算符号“and”和“or” ,同样也是表达式的基本组成元素
  • 非终结符表达式角色——句子节点(SentenceNode):类似于“1 and 1”这样的表达式或者更长的组合表达式
  • 上下文类角色——处理者(Handler):保存输入的表达式和输出的结果
    由此,本例的UML实例图如下:

11.3.1 抽象表达式

// 抽象表达式类
class AbstractNode
{public:AbstractNode(){}// 声明抽象接口virtual char interpret() = 0;
};

11.3.2 终结符表达式角色——值节点

// 终结符表达式:ValueNode
class ValueNode :public AbstractNode
{public :ValueNode(){}ValueNode(int iValue){this->value = iValue;}// 实现解释操作char interpret(){return value;}
private:int value;
};

11.3.3 终结符表达式角色——运算符节点

// 终结符表达式:OperationNode
class OperatorNode :public AbstractNode
{public:OperatorNode(){}OperatorNode(string iOp){this->op = iOp;}// 实现解释操作char interpret(){if (op == "and"){return '&';}else if (op == "or"){return '|';}return 0;}
private:string op;
};

11.3.4 非终结符表达式角色——句子节点

每一个句子节点由“左值节点+运算符节点+右值节点”组成。

// 非终结符表达式:SentenceNode
class SentenceNode :public AbstractNode
{public:SentenceNode(){}SentenceNode(AbstractNode *iLeftNode,AbstractNode *iRightNode, AbstractNode* iOperatorNode){this->leftNode = iLeftNode;this->rightNode = iRightNode;this->operatorNode = iOperatorNode;}char interpret(){if (operatorNode->interpret() == '&'){return leftNode->interpret()&rightNode->interpret();}else{return leftNode->interpret()|rightNode->interpret();}return 0;}
private:AbstractNode *leftNode;AbstractNode *rightNode;AbstractNode *operatorNode;
};

11.3.5 上下文角色——处理者

处理者将处理输入的表达式,并解释出表达式最终的结果。

// 处理者
class Handler
{public:Handler(){}void setInput(string iInput){this->input = iInput;}void handle(){AbstractNode      *left = NULL;AbstractNode     *right = NULL;AbstractNode        *op = NULL;AbstractNode  *sentence = NULL;string iInput = this->input;vector<string>inputList;char* inputCh = const_cast<char*>(iInput.c_str());char *token = strtok(inputCh, " ");while (token != NULL){inputList.push_back(token);token = strtok(NULL, " ");}for (int i = 0; i < inputList.size() - 2; i += 2){left = new ValueNode(*(inputList[i].c_str()));op = new OperatorNode(inputList[i + 1]);right = new ValueNode(*(inputList[i+2].c_str()));sentence = new SentenceNode(left, right, op);inputList[i + 2] = string(1, sentence->interpret());}string tmpRes = inputList[inputList.size() - 1];if (tmpRes == "1"){result = 1;}else if (tmpRes == "0"){result = 0;}else{result = -1;}this->output();}void output(){printf("%s = %d\n", input.c_str(), result);}
private:string input;char result;
};

11.3.6 客户端代码示例和结果

#include <iostream>
#include "InterpreterPattern.h"int main()
{Handler *handler = new Handler();string input_1 = "1 and 1";string input_2 = "1 and 0";string input_3 = "0 and 1";string input_4 = "0 and 0";string input_5 = "0 or 0";string input_6 = "0 or 1";string input_7 = "1 or 0";string input_8 = "1 or 1";string input_9 = "1 and 0 or 1";string input_10 = "0 or 0 and 1";string input_11 = "1 or 1 and 1 and 0";string input_12 = "0 and 1 and 1 and 1";string input_13 = "0 and 1 and 1 and 1 or 1 or 0 and 1";handler->setInput(input_1); handler->handle();handler->setInput(input_2); handler->handle();handler->setInput(input_3); handler->handle();handler->setInput(input_4); handler->handle();handler->setInput(input_5); handler->handle();handler->setInput(input_6); handler->handle();handler->setInput(input_7); handler->handle();handler->setInput(input_8); handler->handle();handler->setInput(input_9); handler->handle();handler->setInput(input_10); handler->handle();handler->setInput(input_11); handler->handle();handler->setInput(input_12); handler->handle();handler->setInput(input_13); handler->handle();printf("\n\n");system("pause");return 0;
}

11.3.7 运行结果

11.4 总结

优点:

  • 易于改变和扩展文法,在解释器中使用类表示语言的文法规则,可以通过继承等机制类改变或扩展文法;
  • 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言;
  • 如果要增加新的解释表达式,只需增加一个新的终结符表达式或非终结符表达式类,无需修改原有代码,符合开闭原则。

缺点:

  • 对于复杂文法难以维护。在解释器模式中每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会大量增加,导致系统难以管理和维护;
  • 执行效率低,因为解释器模式中有大量循环和递归调用。

适用环境:

  • 一些重复出现的问题可以用一种简单的语言进行表达;
  • 一个语言的文法较为简单;
  • 不考虑执行效率的问题时可以使用解释器模式。

架构之路_十一种行为型设计模式相关推荐

  1. Java实现二十三种设计模式(五)—— 十一种行为型模式 (中)——解释器模式、迭代器模式、中介者模式、备忘录模式

    Java实现二十三种设计模式(五)-- 十一种行为型模式 (中)--解释器模式.迭代器模式.中介者模式.备忘录模式 一.解释器模式 我国 IT 界历来有一个汉语编程梦,虽然各方对于汉语编程争论不休,甚 ...

  2. 三电平igbt死区时间计算_一种T型三电平IGBT互补死区驱动电路的制作方法

    本发明涉及光伏三电平逆变器技术领域,具体涉及一种T型三电平IGBT互补死区驱动电路. 背景技术: 目前,国家能源局等三部委推出光伏"领跑者"计划,旨在促进先进技术产品应用和产业升级 ...

  3. 怎么形容智能冰激凌机器人_一种人机交互型冰激凌多功能自动售卖机器人的制作方法...

    本发明涉及智能机器人技术领域,具体为一种人机交互型冰激凌多功能自动售卖机器人. 背景技术: 冰淇淋(ice cream),是以饮用水.牛奶.奶粉.奶油(或植物油脂).奶油食糖等为主要原料,加入适量食品 ...

  4. xp透明膜p系列_一种XPP型超厚医用吸塑包装膜的制作方法

    本实用新型涉及包装膜领域,具体是指一种XPP型超厚医用吸塑包装膜. 背景技术: 医用吸塑包装膜主要用于一次性预成型无菌系统的纸塑易撕.塑塑易撕的医疗器械用品的包装.目前的医用吸塑包装膜的厚度一般在0. ...

  5. 单电源运算放大器全波整流电路_几种二极管整流电路原理图解。

    在小功率直流电源中,常见的几种整流电路有单相半波.全波.桥式和三相整流电路等:全波整流电路是平常应用中用得非常多的电路图之一,全波整流电路是指能够把交流转换成单一方向电流的电路,最少由两个整流器合并而 ...

  6. fpga初始化错误_一种SRAM型FPGA单粒子效应加固平台设计

    随着半导体技术的高速发展,大规模集成电路变得更加复杂,开发周期变得更长.FPGA由于具备可编程性,其广泛应用可以降低电路的开发成本.然而,单粒子翻转(SEU)会使FPGA内部的大量的存储器变得不可靠, ...

  7. 软甲架构设计软件_几种常用软件架构设计指南

    几种常用软件架构设计指南 软件架构( software architecture )是一系列相关的抽象模式,用于指导大型 软件系统各个方面的设计. 软件架构是一个系统的草图. 软件架构描述的对象是 直 ...

  8. java设计模式(一)——五种创建型设计模式

    一.什么是设计模式? 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. ...

  9. Java设计模式(二)创建型设计模式

    文章目录 三 创建型设计模式 3.1 单例设计模式 3.1.1 饿汉式(线程安全) 3.1.2 懒汉式(线程不安全) 3.1.3 优缺点 3.1.4 补充 3.1.5 框架中的使用 3.1.4.1 S ...

最新文章

  1. yarn的组成部分_图解YARN工作原理
  2. 题目1112:拦截导弹
  3. 形象解释Momentum
  4. java 头尾 队列_探索JAVA并发 - 并发容器全家福
  5. product text的language dropdown list里 没有对应语言的问题
  6. linux3.4 内核裁剪,05-S3C2440学习之内核(移植)linux3.4.2移植(2)之yffs2文件系统移植+内核裁剪+内核制作补丁...
  7. 前端学习(1320):同步和异步得区别
  8. java 数据保存内存_java中的各种数据类型在内存中存储的方式 一
  9. 从0到1打造企业数字化运营闭环白皮书
  10. 苹果要换Type-C接口?丁磊建议统一充电器接口 工信部回复来了...
  11. sqlserver 导入mysql,在项目中迁移MS SQLServer到Mysql数据库,实现MySQL数据库的快速整合...
  12. 携程第二场预赛 1003:位图像素的颜色(水题,判断点是否在矩形内)
  13. linux shell 随机字符生成单词
  14. BPSK、8PSK、QPSK、16QAM、64QAM区别与联系
  15. java替换的程序_Java文本文件批量替换小程序的方法
  16. 谷歌发布adb-fastboot工具独立包
  17. wincc7.5下载安装教程(Win10系统)
  18. 手动调整 HP ML350 Gen9 服务器风扇转速
  19. LINGO11 百度网盘
  20. java 读音_数的读法 (Java代码)

热门文章

  1. (附源码)计算机毕业设计SSM化妆品销售购物系统
  2. 使用 Sanic 框架进行 Python Web 开发
  3. python框架sanic_Sanic框架路由用法实例分析
  4. 盘点李念演艺之路 《蜗居》过后未来大猜想(图)
  5. FFN(mlpack)
  6. 如何选择一个适合自己并且有前景的职业?
  7. [高通MSM8909][Android7.1]移除电池显示选项
  8. 关于微信开发的自动回复消息和客服消息,文本带链接跳转
  9. 理解浏览器的进程与线程
  10. 明治乳业“解锁乳酸菌之旅”趣味科普展览限时亮相虹桥南丰城;佛罗伦萨小镇开启10周年狂欢购 | 知消...