From ad6465469c933824e7df516950b274e5ff604084 Mon Sep 17 00:00:00 2001 From: fengqiangguo Date: Sun, 14 Apr 2024 11:25:39 +0800 Subject: [PATCH] add some pattern markdown. --- 17.IteratorPattern/17.IteratorPattern.md | 196 ++++++- 18.MediatorPattern/18.MediatorPattern.md | 310 +++++++++++ 19.MementoPattern/19.MementoPattern.md | 337 ++++++++++++ 20.ObserverPattern/20.ObserverPattern.md | 345 ++++++++++++ 21.StatePattern/21.StatePattern.md | 518 ++++++++++++++++++ 22.StrategyPattern/22.StrategyPattern.md | 237 ++++++++ .../23.TemplateMethodPattern.md | 231 ++++++++ 7 files changed, 2173 insertions(+), 1 deletion(-) create mode 100644 18.MediatorPattern/18.MediatorPattern.md create mode 100644 19.MementoPattern/19.MementoPattern.md create mode 100644 20.ObserverPattern/20.ObserverPattern.md create mode 100644 21.StatePattern/21.StatePattern.md create mode 100644 22.StrategyPattern/22.StrategyPattern.md create mode 100644 23.TemplateMethodPattern/23.TemplateMethodPattern.md diff --git a/17.IteratorPattern/17.IteratorPattern.md b/17.IteratorPattern/17.IteratorPattern.md index 98fc7e2..acbeea8 100644 --- a/17.IteratorPattern/17.IteratorPattern.md +++ b/17.IteratorPattern/17.IteratorPattern.md @@ -16,4 +16,198 @@ - **Aggregate(抽象聚合类)**:用于存储和管理元素对象,声明一个创建迭代器的接口,其实是一个抽象迭代器工厂的角色。 - **ConcreteAggregate(具体聚合类)**:实现了方法createIterator(),该方法返回一个与该具体聚合类对应的具体迭代器ConcreteIterator的实例。 -![avatar](https://github.com/FengJungle/DesignPattern/blob/master/16.InterpreterPattern/1.Picture/%E5%BC%95%E8%A8%80.png) \ No newline at end of file +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/17.IteratorPattern/1.Picture/%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +## 3.迭代器模式代码实例 +``` +电视机遥控器是迭代器的一个现实应用,通过它可以实现对电视频道集合的遍历操作,电视机可以看成一个存储频道的聚合对象。本例Jungle将采用迭代器模式来模拟遥控器操作电视频道的过程。 +``` +很明显,遥控器是一个具体的迭代器,具有上一个频道previous() 、下一个频道next()、当前频道currentChannel()等功能;需要遍历的聚合对象是电视频道的集合,即电视机。本例的UML图如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/17.IteratorPattern/1.Picture/%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) + +### 3.1.抽象聚合类和具体聚合类 +``` +#ifndef __AGGREGATE_H__ +#define __AGGREGATE_H__ + +#include +#include +using namespace std; + +// 前向声明,因为两个类互相引用 +class Iterator; +class RemoteControl; + +// 抽象聚合类 Aggregate +class Aggregate +{ +public: + Aggregate(){} + virtual ~Aggregate(){} + virtual Iterator* createIterator() = 0; +}; + +// 具体聚合类 Television +class Television :public Aggregate +{ +public: + Television(); + Television(vector iChannelList); + // 实现创建迭代器 + Iterator* createIterator(); + // 获取总的频道数目 + int getTotalChannelNum(); + void play(int i); +private: + vector channelList; +}; + +#endif //__AGGREGATE_H__ +``` +实现: +``` +#include "Iterator.h" + +Television::Television(){} + +Television::Television(vector 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.2.抽象迭代器 +``` +// 抽象迭代器 +class Iterator +{ +public: + Iterator(){} + virtual ~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; +private: + +}; +``` +### 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.4.客户端代码示例及结果 +``` +#include +#include "Iterator.h" + +int main() +{ + vector 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"); + + delete tv; + delete remoteControl; + + return 0; +} +``` +结果如下图: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/17.IteratorPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +## 4.总结 +观察上述代码可发现,迭代器类和聚合类存在相互包含相互引用的关系,因此代码里需要前向声明某个类(具体操作见上,代码资源见GitHub - FengJungle/DesignPattern: Design pattern demo code)。 + +- 优点: + - 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多个遍历方式。 + - 简化了聚合类,使得聚合类的职责更加单一; + - 迭代器模式中引入抽象层,易于增加新的迭代器类,便于扩展,符合开闭原则。 +- 缺点: + - 将聚合类中存储对象和管理对象的职责分离,增加新的聚合类时同样需要考虑增加对应的新的迭代器类,类的个数成对增加,不利于系统管理和维护; + - 设计难度较大,需要充分考虑将来系统的扩展。 +- 适用环境: + - 访问一个聚合对象而无需暴露它的内部结构; + - 需要为一个聚合对象提供多种遍历方法。 \ No newline at end of file diff --git a/18.MediatorPattern/18.MediatorPattern.md b/18.MediatorPattern/18.MediatorPattern.md new file mode 100644 index 0000000..3ba6c34 --- /dev/null +++ b/18.MediatorPattern/18.MediatorPattern.md @@ -0,0 +1,310 @@ +# 中介者模式,说一说贝壳找房 +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/18.MediatorPattern/1.Picture/%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F.jpg) +``` +离开学校参加工作之前,你一定是有一段时间是在找租,Jungle也是如此。 +Jungle为了找到合适的房子,沿着地铁线一个小区一个小区的去问门卫问保安,或者照着小区门口展板上的房东的联系方式去找房东……此事已经过去大半年了,但Jungle现在想来还是觉得很麻烦!麻烦在哪里?得亲自走亲自联系各个房东,通信录和微信得加好多房东…… +其实有更省事的办法,那就是找中介,租房中介哪儿都是。虽然贵(主要原因),但是的确为租客省了很多事,其实也为房东省了很多事。 +``` +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/18.MediatorPattern/1.Picture/%E4%B8%AD%E4%BB%8B%E8%80%85%E4%B8%BE%E4%BE%8B%E5%9B%BE.png) + +## 1.中介者模式简介 +上述Jungle租房的例子如上图,如果Jungle自己去租房,得和各个房东亲自交互,如果另一个租客贱萌兔也在自己找房,同样也得和很多房东打交道。房东也是一样,得和众多不同的租客联系。**如果有中介者了,房东们只需要去中介者那里注册一下,自己的房子在哪儿、什么户型设施、价格多少,就ok了;Jungle和贱萌兔也只需要和一个人打交道,那就是中介。中介的出现使两边都省去了不少事**。 + +软件设计模式中,也有一种类似的解决方案,那就是中介者模式—— +``` +中介者模式: +定义一个对象来封装一系列对象的交互。中介者模式使各个对象之间不需要显示地相互引用,从而使其耦合松散,而且用户可以独立地改变它们之间的交互。 +``` +如果一个系统里各个对象之间存在多对多的相互关系,可以将对象之间的一些交互行为从各个对象中分离出来,集中封装在一个中介者对象中,使其耦合松散,并由中介者统一协调。通过中介者,对象之间的多对多关系就简化了相对更简单的一对多关系。 + +## 2.中介者模式结构 +中介者模式的UML图如下,为了便于扩展,系统引入了抽象中介者。 +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/18.MediatorPattern/1.Picture/%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +由图可知,中介者模式主要有以下角色: +- **Mediator(抽象中介者)**:声明一个用于与各个同事对象之间交互的接口,通常声明一个注册方法,用于增加同事对象; +- **ConcreteMediator(具体中介者)**:实现上面的接口,协调各个同事对象来实现协作行为,维持对各个同事对象的引用; +- **Colleague(抽象同事类)**:声明各个同事类公有的接口,同时维持了一个对抽象中介者类的引用; +- **ConcreteColleague(具体同事类)**: 具体实现接口,具体同事类只需与中介者通信,通过中介者完成与其他同事类的通信。 + +**中介者模式的核心在于引入了中介者类**,中介者类承担了两个层次的职责: +- 结构上起**中转作用**:通过中介者的中转,各个同事之间不必再相互显示调用或引用,只需通过中介者实现间接调用的目的; +- 行为上起**协调作用**:中介者可以进一步地将同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不必指出中介者具体该如何操作,中介者根据封装在自身内部的协调逻辑对同事的请求进一步处理,将同事成员之间的关系行为进行分离和封装。 +## 3.中介者模式代码实例 +本节Jungle将采用中介者模式模拟“租客——房租中介——房东”之间的爱恨情仇! +``` +Jungle和贱萌兔想要通过房屋中介(Agency)租房,需要去中介处了解房东(Landlord)的信息(姓名,价格,地址和联系方式);房东们(Landlord)需要在中介处注册自己的房源,同时也可以从中介处了解租客(Tenant)的信息(姓名)。 +``` +本例的UML图如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/18.MediatorPattern/1.Picture/%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) + +### 3.0.公共头文件 +为区分房东和租客,Jungle定义了一个枚举类型和对应的setter、getter方法: +``` +#ifndef __COMMON_H__ +#define __COMMON_H__ + +// 公共头文件 + +#include +using namespace std; + +enum PERSON_TYPE +{ + NONE_PERSON, + LANDLORD, + TENANT +}; + +#endif //__COMMON_H__ +``` +### 3.1.中介者 +#### 3.1.1.抽象中介者 +``` +// 抽象中介者 +class Mediator +{ +public: + Mediator(){} + virtual ~Mediator(){} + // 声明抽象方法 + virtual void operation(Colleague*) = 0; + // 声明注册方法 + virtual void registerMethod(Colleague*) = 0; +}; +``` +#### 3.1.2.具体中介者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: + vectorlandlordList; + vectortenantList; +}; +``` +### 3.2.同事类 +#### 3.2.1.抽象同事类 +``` +#include "common.h" +using namespace std; + +// 前向声明 +class Mediator; +class Agency; + +// 抽象同事类 +class Colleague +{ +public: + Colleague(){} + virtual ~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); +} +``` +#### 3.2.3.具体同事类——租客(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()); +} +``` +### 3.3.客户端代码示例及效果 +``` +#include +#include "Mediator.h" +#include "Colleague.h" + +int main() +{ + // 创建租房中介 + Agency *mediator = new Agency(); + + // 创建3位房东 + Landlord *fangdong1 = new Landlord("刘备", 1350, "成都市双流区", "1351025"); + Landlord *fangdong2 = new Landlord("关羽", 1500, "成都市武侯区", "1378390"); + Landlord *fangdong3 = new Landlord("张飞", 1000, "成都市龙泉驿", "1881166"); + 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"); + + delete mediator; + delete fangdong1; + delete fangdong2; + delete fangdong3; + delete jungle; + delete jianmengtu; + mediator = nullptr; + fangdong1 = nullptr; + fangdong2 = nullptr; + fangdong3 = nullptr; + jungle = nullptr; + jianmengtu = nullptr; + + return 0; +} +``` +运行结果如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/18.MediatorPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +## 4.总结 +- 优点: + - 简化了对象之间的交互,通过中介者,对象之间的多对多关系就简化了相对更简单的一对多关系; + - 可将各个同事对象解耦,利于各个同事之间的松散耦合,可独立地改变和复用每一个同事对象,增加新的中介者和同事都比较方便,符合开闭原则; + - 可减少子类生成,将原本分布于多个对象之间的行为封装在一起,只需生成新的具体中介者类就可以改变这些行为。 +- 缺点: + - 具体中介者类中包含了大量与同事之间交互的细节和逻辑,可能使得中介者类很复杂以至于难以管理维护。 +- 适用环境: + - 系统中的对象之间存在复杂的交互关系,使得系统内逻辑错综复杂,难以管理; + - 一个对象引用了其他很多对象,并直接和这些对象交互,导致该对象难以复用。 \ No newline at end of file diff --git a/19.MementoPattern/19.MementoPattern.md b/19.MementoPattern/19.MementoPattern.md new file mode 100644 index 0000000..4cc9525 --- /dev/null +++ b/19.MementoPattern/19.MementoPattern.md @@ -0,0 +1,337 @@ +# 我用备忘录模式设计了简易的版本控制系统 +``` +“Ctrl+Z”是什么操作?各位都用过,并且经常使用吧?撤销!撤销上一个操作返回上一个状态,甚至撤销好几个操作,返回到几个操作之前的状态。这个操作非常有用,一旦我们某一步操作失误,可以选择撤销操作来返回原来的无错状态。 +那么系统怎么知道每一步的状态呢?它一定保存了一定数量的历史状态!就像Git版本控制一样,保存着每一次提交的状态,使用者可以随时reset到历史某个状态,就像一个备忘录一样,保存了某些阶段的状态。 +``` + +## 1.备忘录模式简介 +类似于上述引言的例子,在软件系统的操作过程中,难免会出现一些不当的操作,使得系统状态出现某些故障。如果能够有一种机制——**能够保存系统每个阶段的状态,当用户操作失误的时候,可以撤销不当的操作,回到历史某个阶段**——那么软件系统将更加灵活和人性化。 + +有没有这样的一种解决方案呢?有!那就是备忘录模式。备忘录模式提供了一种状态恢复的机制,用户可以方便地回到指定的某个历史状态。很多软件的撤销操作,就使用了备忘录模式。 +``` +备忘录模式: +在不破坏封装的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。 +``` +## 2.备忘录模式结构 +备忘录模式的UML图如下所示: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/19.MementoPattern/1.Picture/%E5%A4%87%E5%BF%98%E5%BD%95%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +备忘录模式主要有以下角色: +- **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 +``` +## 3.备忘录模式代码实例 +``` +Jungle正在为代码版本管理苦恼,有时候为了尝试某个功能就去修改代码,导致原有的健壮的代码被破坏。所以Jungle希望能够设计一个代码保存和版本回退功能的demo,方便代码的管理。 +``` +本实例中,原生器为CodeVersion,具有版本号version、提交日期date和标签label三个状态需要备忘录Memento保存;管理者是CodeManager,具有提交代码commit(即保存一个版本)、回退到指定版本switchToPointedVersion(即撤销操作)和查看提交历史codeLog的功能。该实例的UML图如下图,具体代码如下 + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/19.MementoPattern/1.Picture/%E5%A4%87%E5%BF%98%E5%BD%95%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) +### 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 +``` +### 3.2.原生器CodeVersion +``` +#ifndef __CODEVERSION_H__ +#define __CODEVERSION_H__ + +#include +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 +``` +### 3.3.管理者CodeManager +``` +#ifndef __CODEMANAGER_H__ +#define __CODEMANAGER_H__ + +#include "Memento.h" +#include +using namespace std; + +// 管理者 +class CodeManager +{ +public: + CodeManager(){} + CodeManager(const CodeManager&) = delete; + CodeManager& operator=(const CodeManager&) = delete; + ~CodeManager(){ + for(auto* memento: mementoList){ + if(memento){ + delete memento; + memento = nullptr; + } + } + } + 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 mementoList; +}; + +#endif +``` +### 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"); + + delete Jungle; + delete codeVer; + Jungle = nullptr; + codeVer = nullptr; + + return 0; +} +``` +代码运行结果如下: +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/19.MementoPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +这是不是像一个超级简易版本的代码版本控制系统???哈哈哈! + +## 4.总结 +- 优点: + - 实现状态恢复、撤销操作的功能,用户可以恢复到指定的历史状态,让软件系统更加人性化; + - 备忘录封装了信息,除了原生器以外,其他对象访问不了备忘录的代码; +- 缺点: + - 资源消耗大。如果需要保存原生器对象的多个历史状态,那么将创建多个备忘录对象;或者如果原生器对象的很多状态都需要保存,也将消耗大量存储资源。 +- 适用环境: + - 保存一个对象的历史状态,系统需要设计回退或者撤销功能; + - 备忘录类可以封装一个对象的历史状态,避免对象的历史状态被外界修改。 diff --git a/20.ObserverPattern/20.ObserverPattern.md b/20.ObserverPattern/20.ObserverPattern.md new file mode 100644 index 0000000..a353931 --- /dev/null +++ b/20.ObserverPattern/20.ObserverPattern.md @@ -0,0 +1,345 @@ +# “牵一发而动全身”——我用观察者模式简单模拟吃鸡 +``` +每当Jungle公众号【Jungle笔记】更新发布了文章,作为Jungle的关注者,你会第一时间接到消息,(如果)然后就可以去查看、点赞、评论和转发,接下来的一天你都高高兴兴; +每当Jungle更新了CSDN博客,作为Jungle的支持者,你也会在打开CSDN网站的时候看到消息,(如果)然后你就可以去查看、点赞、评论和转发,接下来的一周你都高高兴兴。 +``` +也就是说,“Jungle更新发布文章”这个行为可能会导致“关注者查看、评论、点赞”等一系列行为。这表明“Jungle更新发布文章”并不是孤立的,而是与众多对象产生了关联。**一个对象行为的改变,其相关联的对象都会得到通知,并自动产生对应的行为。这在软件设计模式中,即是观察者模式**。 + +## 1.观察者模式简介 +软件系统中的对象并不是孤立存在的,一个对象行为的改变可能会引起其他所关联的对象的状态或行为也发生改变,即“牵一发而动全身”。观察者模式建立了一种一对多的联动,一个对象改变时将自动通知其他对象,其他对象将作出反应。观察者模式中,发生改变的对象称为“观察目标”,被通知的对象称为“观察者”。一个观察目标可以有很多个观察者。 + +观察者模式定义如下: +``` +观察者模式: +定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。 +``` + +观察者模式又被称为发布-订阅模式(Publish-Subscribe)、模型-视图模式(Model-View)、源-监听器模式(Source-Listener)、从属者模式(Dependents)。 + +## 2.观察者模式结构 +观察者模式由观察者和观察目标组成,为便于扩展,两个角色都设计了抽象层。观察者模式的UML图如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/20.ObserverPattern/1.Picture/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +- **Subject(目标)**:是被观察的对象,目标中定义了一个观察者的集合,即一个目标可能会有多个观察者,通过attach()和detach()方法来增删观察者对象。目标声明了通知方法notify(),用于在自身状态发生改变时通知观察者。 +- **ConcreteSubject(具体目标)**:具体目标实现了通知方法notify(),同时具体目标有记录自身状态的属性和成员方法; +- **Observer(观察者)**:观察者将对接收到的目标发生改变的通知做出自身的反应,抽象层声明了更新方法update(); +- **ConcreteObserver(具体观察者)**: 实现了更新方法update(),具体观察者中维护了一个具体目标对象的引用(指针),用于存储目标的状态。 + +下述是观察者模式的典型实现: +``` +#define __DEMO_H__ +#ifdef __DEMO_H__ + +using namespace std; +#include + +// 抽象观察者 +class Observer +{ +public: + virtual ~Observer() {} + // 声明响应更新方法 + virtual void update() = 0; +}; + +// 具体观察者 +class ConcreteObserver : public Observer +{ +public: + // 实现响应更新方法 + void update() + { + // 具体操作 + } +}; + +// 抽象目标 +class Subject +{ +public: + virtual ~Subject() {} + // 添加观察者 + void attach(Observer *obs) + { + obsList.push_back(obs); + } + // 移除观察者 + void detach(Observer *obs) + { + obsList.remove(obs); + } + // 声明通知方法 + virtual void notify() = 0; + +protected: + // 观察者列表 + list 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(); + + delete sub; + delete obs; + return 0; +} +#endif +``` +## 3.观察者模式代码实例 +``` +玩过和平精英这款游戏吗?四人组队绝地求生,当一个队友发现物资时,可以发消息“我这里有物资”,其余三个队友听到后可以去取物资;当一个队友遇到危险时,也可以发消息“救救我”,其余三个队友得到消息后便立马赶去营救。本例Jungle将用观察者模式来模拟这个过程。 +``` +本例的UML图如下: +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/20.ObserverPattern/1.Picture/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) + +本例中,抽象观察者是Observer,声明了发现物资或者需要求救时的呼叫的方法call(),具体观察者是Player,即玩家,Player实现了呼叫call()方法,并且还定义了取物资come()和支援队友help()的方法。本例定义了AllyCenter作为抽象目标,它维护了一个玩家列表playerList,并且定义了加入战队和剔除玩家的方法。具体目标是联盟中心控制器AllyCenterController,它实现了通知notify()方法,该方法将队友call的消息传达给玩家列表里的其余队友,并作出相应的响应。 + +### 3.0.公共头文件 +通过一个枚举类型来定义两种消息类型,即发现物资和求助 +``` +#ifndef __COMMON_H__ +#define __COMMON_H__ + +enum INFO_TYPE{ + NONE, + RESOURCE, + HELP +}; + +#endif //__COMMON_H__ +``` +### 3.1.观察者 +#### 3.1.1.抽象观察者Observer +``` +// 抽象观察者 Observer +class Observer +{ +public: + virtual ~Observer(){} + Observer(){} + // 声明抽象方法 + virtual void call(INFO_TYPE infoType, AllyCenter* ac) = 0; + string getName(){ + return name; + } + void setName(string iName){ + this->name = iName; + } +private: + string name; +}; +``` +#### 3.1.2.具体观察者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()); + } +}; +``` +### 3.2.目标类 +#### 3.2.1.抽象目标AllyCenter +声明 +``` +// 前向声明 +class Observer; +class Player; + +// 抽象目标:联盟中心 +class AllyCenter +{ +public: + AllyCenter(); + virtual ~AllyCenter() {} + // 声明通知方法 + virtual void notify(INFO_TYPE infoType, std::string name) = 0; + // 加入玩家 + void join(Observer *player); + // 移除玩家 + void remove(Observer *player); + +protected: + // 玩家列表 + std::vector 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); +} +``` +#### 3.2.2.具体目标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"); + } +} +``` +### 3.3.客户端代码示例及效果 +``` +#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"); + + delete controller; + delete Jungle; + delete Single; + delete Jianmengtu; + delete SillyDog; + controller = nullptr; + Jungle = nullptr; + Single = nullptr; + Jianmengtu = nullptr; + SillyDog = nullptr; + + return 0; +} +``` +上述代码运行结果如下图: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/20.ObserverPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +## 4.观察者模式的应用 +观察者模式是一种使用频率非常高的设计模式,几乎无处不在。凡是涉及一对一、一对多的对象交互场景,都可以使用观察者会模式。比如购物车,浏览商品时,往购物车里添加一件商品,会引起UI多方面的变化(购物车里商品数量、对应商铺的显示、价格的显示等);各种编程语言的GUI事件处理的实现;所有的浏览器事件(mouseover,keypress等)都是使用观察者模式的例子。 + +## 5.总结 +- 优点: + - 观察者模式实现了稳定的消息更新和传递的机制,通过引入抽象层可以扩展不同的具体观察者角色; + - 支持广播通信,所有已注册的观察者(添加到目标列表中的对象)都会得到消息更新的通知,简化了一对多设计的难度; + - 符合开闭原则,增加新的观察者无需修改已有代码,在具体观察者与观察目标之间不存在关联关系的情况下增加新的观察目标也很方便。 +- 缺点: + - 代码中观察者和观察目标相互引用,存在循环依赖,观察目标会触发二者循环调用,有引起系统崩溃的风险; + - 如果一个观察目标对象有很多直接和简介观察者,将所有的观察者都通知到会耗费大量时间。 +- 适用环境: + - 一个对象的改变会引起其他对象的联动改变,但并不知道是哪些对象会产生改变以及产生什么样的改变; + - 如果需要设计一个链式触发的系统,可是使用观察者模式; + - 广播通信、消息更新通知等场景。 \ No newline at end of file diff --git a/21.StatePattern/21.StatePattern.md b/21.StatePattern/21.StatePattern.md new file mode 100644 index 0000000..ce78cd1 --- /dev/null +++ b/21.StatePattern/21.StatePattern.md @@ -0,0 +1,518 @@ +# 状态模式——从斗地主开始说起 +``` +“人有悲欢离合,月有阴晴圆缺”。很多事物在特定条件下转换成不同的状态,在不同状态下表现出不同的行为。 +``` +在软件系统中,有些对象在不同的条件下也具有不同的状态,不同状态之间可以相互转换。通过判断不同的条件分支(if...else...或者switch..case...)可以进行状态的转换。但这样势必使得代码的判断逻辑变得复杂,降低系统的可维护性。如果新加入一种状态,还需要修改判断逻辑,不符合开闭原则。 + +为解决复杂对象的多种状态转换问题,并使客户端代码与对象状态之间的耦合度降低,可以使用状态模式。 + +## 1.状态模式简介 +状态模式将一个对象的状态从对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化。对于客户端而言,无需关心对象转态的转换以及对象所处的当前状态,无论处于何种状态的对象,客户端都可以一致处理。 +``` +状态模式: +允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 +``` +## 2.状态模式结构 +状态模式的UML图如下: +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/21.StatePattern/1.Picture/%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +状态模式引入了抽象层,具有抽象状态类和具体状态类,还包括一个上下文境类: +- **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 + } + } +}; +``` +下面是状态模式的典型用法: +``` +// 抽象状态类 +class State +{ +public: + virtual ~State(){} + // 声明抽象方法 + 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 something + state->handle(); + } +private: + // 引用状态对象 + State *state; +}; +``` +3.状态模式代码实例 +接下来Jungle用一个实例来应用状态模式。 +``` +在某纸牌游戏中,游戏人物分为入门级(primary)、熟练级(Secondary)、高手级(Professional)和骨灰级(Final)四种级别,由人物的积分来划分角色等级,游戏胜利将增加积分,失败将扣除积分。入门级有最基本的游戏功能play(),熟练级增加了游戏胜利积分加倍功能doubleScore(),高手级在熟练级的基础上增加了换牌功能changeCards(),骨灰级在高手级的基础上再增加了偷看他人纸牌的功能peekCards()。 +积分规则如下: +基础分:100,游戏胜利+50分,游戏失败+30分; +入门级:0~150;熟练级150~200; +高手级:200~250; +骨灰级:250以上 +``` +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/21.StatePattern/1.Picture/%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F%E4%B8%BE%E4%BE%8B.png) +本例设计游戏账户GameAccount为上下文类,维护了一个级别类(Level)的对象实例。GameAccount中定义了一个代表积分的score整型和统一封装的方法playcard(),在该方法中再调用具体级别的各个技能方法。采用随机数的方式来随机判定牌局的输赢,以增减积分。 + +级别类Level为抽象类,声明了play()、doubleScore()、changeCards()、seekCards()的抽象方法,在四个具体级别类Primary、Secondary、Professional和Final类中具体实现了该方法,具体来说是根据该级别是否有权利使用该技能来打印一行话。upgradeLevel()方法用于判断每局牌结束后该游戏账户的积分是否可以升级或者降级,通过setLevel()方法改变当前账户的游戏级别。 + +该实例的UML图如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/21.StatePattern/1.Picture/%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) + +### 3.1.上下文类:游戏账户类 +``` +#ifndef __GAMEACCOUNT_H__ +#define __GAMEACCOUNT_H__ + +using namespace std; +#include +// 前向声明 +class Level; + +class GameAccount +{ +public: + GameAccount(); + GameAccount(string iName); + GameAccount(const GameAccount&) = delete; + GameAccount& operator=(const GameAccount&) = delete; + ~GameAccount(){ + if(level){ + delete level; + level = nullptr; + } + } + 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 +#include +#define random(x) (rand()%x) + +GameAccount::GameAccount(){ + level = nullptr; + printf("创立游戏角色,积分:100,级别:PRIMARY\n"); + score = 100; + name = "none"; + setLevel(new Primary(this)); +} + +GameAccount::GameAccount(string iName){ + level = nullptr; + printf("创立游戏角色,积分:100,级别:PRIMARY\n"); + score = 100; + name = iName; + setLevel(new Primary(this)); +} + +void GameAccount::setLevel(Level* iLevel){ + if(level != nullptr){ + delete level; + level = nullptr; + } + 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; +} +``` +### 3.2.状态类 +#### 3.2.1.抽象状态类:Level +头文件: +``` +#include "GameAccount.h" + +class Level +{ +public : + Level(); + virtual ~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; +} +``` +#### 3.2.2.具体状态类: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"); + } +} +``` +#### 3.2.3.具体状态类: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"); + } +} +``` +#### 3.2.4.具体状态类: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"); + } +} +``` +#### 3.2.5.具体状态类: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()); + } +} +``` +### 3.3.客户端代码示例及结果 +客户端代码创建了一个游戏账户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"); + + delete jungle; + jungle = nullptr; + + return 0; +} +``` +结果如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/21.StatePattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +上面的代码不管Jungle当前是什么级别,都统一地调用了上下文类封装好的方法playcard(),即外界并不知道不同级别内部的具体实现细节。运行结果显示,Jungle的**在不同的状态(级别)下能够表现不同的行为(不同的技能),并且能够不断改变自身的状态(升级或降级)** + +## 4.总结 +- 优点: + - 状态模式封装了状态转换的规则,只给外界暴露了统一的接口,客户端可以无差别地调用该接口(如上述实例的客户端代码) + - 状态模式将所有与具体状态有关的行为放到一个类(具体状态类)中,只需要注入(依赖)不同的状态类对象到上下文类中,即可使上下文中拥有不同的行为 +- 缺点: + - 状态模式增加了系统中类的个数(不同的具体状态类) + - 结构相对复杂(如前述实例的UML图),代码逻辑也较复杂 + - 如果要增加新的状态,需要修改负责状态转换的代码,不符合开闭原则(如上述实例,如果增加了一个中间级别,是不是得修改很多状态转换的逻辑?) +- 适用环境: + - 对象的行为根据它的状态的改变而不同 + - 代码中含有大量与对象状态有关的判断逻辑(if……else……或switch……case……) diff --git a/22.StrategyPattern/22.StrategyPattern.md b/22.StrategyPattern/22.StrategyPattern.md new file mode 100644 index 0000000..653b8a8 --- /dev/null +++ b/22.StrategyPattern/22.StrategyPattern.md @@ -0,0 +1,237 @@ +# 如何管理和维护算法族?只需知道策略模式 +``` +同样是排序算法,你可以选择冒泡排序、选择排序、插入排序、快速排序等等,也即是说,为了实现排序这一个目的,有很多种算法可以选择。这些不同的排序算法构成了一个算法族,你可以在需要的时候,根据需求或者条件限制(内存、复杂度等)适时选择具体的算法。 +``` +在面向对象的设计里,该如何设计这样一个算法族呢?它包含了多种算法,在使用的时候又会根据条件来选择具体的算法?这就会用到软件设计模式中的——策略模式。 + +## 1.策略模式简介 +策略模式用于算法的自由切换和扩展,对应于解决某一问题的一个算法族,允许用户从该算法族中任意选择一个算法解决问题,同时还可以方便地更换算法或者增加新的算法。策略模式将算法族中的每一个算法都封装成一个类,每一个类称为一个策略(Strategy)。 +``` +策略模式: +定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法可以独立于使用它的客户而变化。 +``` +## 2.策略模式结构 +为了方便算法族中的不同算法在使用中具有一致性,在策略模式中会提供一个抽象层来声明公共接口,在具体的策略类中实现各个算法。策略模式由上下文类和策略类组成,其UML结构如下图: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/22.StrategyPattern/1.Picture/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +- Context(上下文类) :上下文类是使用算法的角色,可以在解决不同具体的问题时实例化不同的具体策略类对象; +- Strategy(抽象策略类):声明算法的方法,抽象层的设计使上下文类可以无差别的调用不同的具体策略的方法; +- ConcreteStrategy(具体策略类):实现具体的算法。 + +## 3.策略模式代码实例 +``` +某系统提供了一个用于对数组进行操作的类,该类封装了对数组的常见操作,现以排序操作为例,使用策略模式设计该数组操作类,使得客户端可以动态更换排序算法,可以根据需要选择冒泡排序或者选择排序或者插入排序,也能够灵活增加新的排序算法 。 +``` +显然,在该实例中,可以冒泡排序、选择排序和插入排序分别封装为3个具体策略类,它们有共同的基类SortStrategy。还需要一个上下文类Context,Context中维护了一个SortStrategy的指针,在客户端需要的时候,通过Context的setSortStrategy()方法来实例化具体的排序类对象。该实例的UML结构图如下: +—— +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/22.StrategyPattern/1.Picture/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) + +### 3.1.排序策略类 +#### 3.1.1.抽象排序策略类 +``` +// 抽象策略类 +class Strategy +{ +public: + Strategy(){} + virtual ~Strategy(){} + virtual void sort(int arr[], int N) = 0; +}; +``` +#### 3.1.2.具体策略类:冒泡排序类 +``` +// 具体策略:冒泡排序 +class BubbleSort :public Strategy +{ +public: + BubbleSort(){ + printf("冒泡排序\n"); + } + void sort(int arr[], int N){ + for (int i = 0; iarr[j + 1]){ + int tmp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = tmp; + } + } + } + } +}; +``` +#### 3.1.3.具体策略类:选择排序类 +``` +// 具体策略:选择排序 +class SelectionSort :public Strategy +{ +public: + SelectionSort(){ + printf("选择排序\n"); + } + void sort(int arr[], int N){ + int i, j, k; + for (i = 0; i= 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; + } + } +}; +``` +### 3.2.上下文类 +``` +#ifndef __CONTEXT_H__ +#define __CONTEXT_H__ + +#include "Strategy.h" +#include + +// 上下文类 +class Context +{ +public: + Context(){ + arr = nullptr; + N = 0; + sortStrategy = nullptr; + } + Context(int iArr[], int iN){ + this->arr = iArr; + this->N = iN; + sortStrategy = nullptr; + } + Context(const Context& context) = delete; + Context& operator=(const Context&) = delete; + ~Context() + { + if(sortStrategy) + { + delete sortStrategy; + sortStrategy = nullptr; + } + } + void setSortStrategy(Strategy* iSortStrategy){ + if(sortStrategy) + { + delete sortStrategy; + sortStrategy = nullptr; + } + 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__ +``` +### 3.3.客户端代码示例及结果 +``` +#include "Context.h" +#include +#include + +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("input:"); + ctx->print(); + + // BubbleSort + ctx->setSortStrategy(new BubbleSort()); + ctx->sort(); + + // SelectionSort + ctx->setSortStrategy(new SelectionSort()); + ctx->sort(); + + // InsertSort + ctx->setSortStrategy(new InsertSort()); + ctx->sort(); + + printf("\n\n"); + system("pause"); + + delete ctx; + ctx = nullptr; + + return 0; +} +``` +代码运行结果如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/22.StrategyPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +从客户端代码可以看到,客户端无需关心具体排序算法的细节,都是统一的调用上下文的sort()接口。另外,如果要增加新的排序算法,比如快速排序QuickSort,只需要从基类SortStrategy在派生一个类QuickSort,在QuickSort类中实现具体的sort()算法即可,扩展起来非常方便。 + +## 4.总结 +- 优点: + - 符合开闭原则,策略模式易于扩展,增加新的算法时只需继承抽象策略类,新设计实现一个具体策略类即可; + - 客户端可以无差别地通过公共接口调用,利用里式替换原则,灵活使用不同的算法策略; + - 提供了一个算法族管理机制和维护机制。 +- 缺点: + - 客户端必须要知道所有的策略,以便在使用时按需实例化具体策略; + - 系统会产生很多单独的类,增加系统中类的数量; + - 客户端在同一时间只能使用一种策略。 +- 适用环境: + - 系统需要在一个算法族中动态选择一种算法,可以将这些算法封装到多个具体算法类中,这些算法类都有共同的基类,即可以通过一个统一的接口调用任意一个算法,客户端可以使用任意一个算法; + - 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装与算法相关的数据结构,可以提高算法的安全性。 diff --git a/23.TemplateMethodPattern/23.TemplateMethodPattern.md b/23.TemplateMethodPattern/23.TemplateMethodPattern.md new file mode 100644 index 0000000..cb8f200 --- /dev/null +++ b/23.TemplateMethodPattern/23.TemplateMethodPattern.md @@ -0,0 +1,231 @@ +# 不知不觉就在使用的一种设计模式——模板方法模式 +类的继承你一定用过,派生类覆写基类的方法你也一定用过,只是你可能不知道,这就是传说中的一种设计模式…… +## 1.模板方法模式简介 +模板方法模式是较简单且常用的一种设计模式,是基于类的继承的一种代码复用技术,其结构只存在基类和派生类之间的继承关系。模板方法是一个具体的方法,给出了一个顶层逻辑流程框架。 +``` +模板方法模式: +定义一个操作中的算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 +``` +## 2.模板方法结构 +模板方法的结构很简单,只有基类和派生类两个角色: +- AbstractClass(基类):即抽象类,在基类中定义或声明了一系列基本操作method,这些操作是具体或者抽象的,每一个操作都对应算法的一个步骤,在其派生类中可以重定义。基类中定义了一个模板方法(template method),它规定了算法的流程框架,模板方法由基类定义或声明的一系列基本操作按照一定流程实现。 +- ConcreteClass(派生类):实现在基类中声明的抽象方法,也可以覆盖在基类中已经实现的方法。 + +模板方法模式的UML图如下: +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/23.TemplateMethodPattern/1.Picture/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +模板方法模式的典型代码如下: +``` +#ifndef __DEMO_H__ +#define __DEMO_H__ + +// 抽象类(基类) +class AbstractClass +{ +public: + virtual ~AbstractClass(){} + // 模板方法,定义一个算法的框架流程 + void templateMethod(){ + // do something + method1(); + method2(); + method3(); + } + // 基本方法——公共方法 + void mehtod1(){ + // do something + } + // 基本方法2 + virtual void method2() = 0; + // 基本方法3——默认实现 + void mehtod3(){ + // do something + } +}; + +// 具体类(派生类) +class ConcreteClass :public AbstractClass +{ +public: + // 实现基本方法2 + void method2(){ + // do something + } + // 重定义基本方法3,覆盖基类的方法3 + void method3(){ + // do something + } +}; + +#endif +``` + +## 3.模板方法模式代码实例 +``` +某个指纹处理模块可以在两种模式下处理算法,即安全模式和非安全模式。在安全模式下,为了保证数据安全,某个指纹识别流程需要对采得的指纹图像进行加密,在处理图像之前再对加密数据进行解密。而非安全模式这不需要加密解密过程。指纹算法流程如下:采图——加密——解密——算法处理指纹——处理结果。现用模板方法模式模拟上述过程。 +``` +在这个实例中,Jungle首先定义了基类FingerprintModule,声明了基本方法:采图getImage()、判断是否在安全模式isSafeMode()、加密encrypt()、解密decrypt()、处理指纹图像processImage()、输出结果output(),在基类中定义了一个模板方法algorithm(),该方法里定义了指纹算法流程。 + +从基类FingerprintModule派生出3个子类,分别是FingerprintModuleA、FingerprintModuleB和FingerprintModuleC,三个子类的特点在于: +- FingerprintModuleA:安全模式,采用RSA秘钥加解密,采用第一代版本算法处理指纹图像; +- FingerprintModuleB:非安全模式,采用第二代版本算法处理指纹图像; +- FingerprintModuleC:安全模式,采用DH秘钥加解密,采用第一代版本算法处理指纹图像; + +该实例的UML图如下: +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/23.TemplateMethodPattern/1.Picture/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) + +### 3.1.基类 + +``` +// 基类 +class FingerprintModule +{ +public: + FingerprintModule() {} + virtual ~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.处理Image + processImage(); + // 4.处理结果 + output(); + } +}; +``` +### 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"); + } +}; +``` +### 3.3.客户端代码实例及效果 +``` +#include "FingerprintModule.h" +#include + +int main() +{ + FingerprintModule *fp = new FingerprintModuleA(); + fp->algorithm(); + delete fp; + fp = nullptr; + + fp = new FingerprintModuleB(); + fp->algorithm(); + delete fp; + fp = nullptr; + + fp = new FingerprintModuleC(); + fp->algorithm(); + delete fp; + fp = nullptr; + + printf("\n\n"); + system("pause"); + + return 0; +} +``` +上述代码运行结果如下: +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/23.TemplateMethodPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +## 4.总结 +模板方法模式是基于类的继承的一种设计模式,使用非常频繁,被广泛应用于框架设计。 +- 优点: + - 在基类中定义算法的框架,并声明一些流程方法,由具体派生类实现细节,派生类中的实现并不会影响基类定义的算法的框架流程; + - 公共行为在基类中提供实现,有利于代码复用; + - 派生类可以覆盖基类的方法,重新实现某些方法,具有灵活性; + - 可以很方便的扩展和更换派生类而不影响基类和其他派生类,符合开闭原则和单一职责原则。 +- 缺点: + - 模板方法模式要为每一个不同的基本方法提供一个派生类,如果基类中基本方法很多,那系统中会定义很多个派生类,导致类的个数很多,系统更加庞大。 +- 适用环境: + - 分割复杂算法,可以将算法的框架流程定义在基类中,设计为模板方法;而具体的细节由派生类设计实现; + - 各个派生类的公共部分提取到基类中,以实现代码复用; + - 派生类需要覆盖基类的某些方法。 \ No newline at end of file