From ad8302eb2a1f807f8d1e09cea03f1c0484e45b5d Mon Sep 17 00:00:00 2001 From: fengqiangguo Date: Sun, 14 Apr 2024 10:42:47 +0800 Subject: [PATCH] add some markdown notes. --- 13.ProxyPattern/ProxyPattern.md | 150 +++++++++ .../ChainOfResponsibility.md | 250 ++++++++++++++ 15.CommandPattern/CommandPattern.md | 311 ++++++++++++++++++ 16.InterpreterPattern/InterpreterPattern.md | 252 ++++++++++++++ 4 files changed, 963 insertions(+) create mode 100644 13.ProxyPattern/ProxyPattern.md create mode 100644 14.ChainOfResponsibility/ChainOfResponsibility.md create mode 100644 15.CommandPattern/CommandPattern.md create mode 100644 16.InterpreterPattern/InterpreterPattern.md diff --git a/13.ProxyPattern/ProxyPattern.md b/13.ProxyPattern/ProxyPattern.md new file mode 100644 index 0000000..e1be1c7 --- /dev/null +++ b/13.ProxyPattern/ProxyPattern.md @@ -0,0 +1,150 @@ +# 双十一天猫购物找代理?先学会代理模式 +``` +“代理”这个词不陌生吧?买化妆品、买奶粉、买包包,都可以通过代理代购,甚至有专门的代购网站; +或者要购置一些自己不太清楚原理好坏的物品,可以找相关代理负责帮忙购买,当然了,得支付一定费用。 +``` +在软件设计模式中,也有一种模式可以提供与代购网站类似的功能。当客户端不能或者不便直接访问一个对象时,可以通过一个称为“代理”的第三方来间接访问,这样的设计模式称为代理模式。 +## 1.代理模式简介 +代理模式在软件设计中广泛应用,而且产生的变种很多,如远程代理、虚拟代理、缓冲代理、保护代理等。 +``` +代理模式: +给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。 +``` +代理模式是一种对象结构型模式,在该模式中引入了一个代理对象,在客户端和目标访问对象之间起到中介的作用。代理对象可以屏蔽或删除客户不想访问的内容和服务,也可以根据客户需求增加新的内容和服务。 + +## 2.代理模式结构 +代理模式的关键是代理类(Proxy)。代理模式中引入了抽象层,客户端针对抽象层编程,这样使得客户端可以一致对待真实对象和代理对象。代理模式主要有**抽象主题角色(Subject)**、**代理主题角色(\(Proxy)**和**真实主题角色(RealSubject)**组成,其UML图如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/13.ProxyPattern/1.Picture/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +- **抽象主题角色(Subject)**:声明了代理主题角色和真实主题角色共同的一些接口,因此在任何可以使用真实主题对象的地方都可以使用代理主题角色(想一想代购是不是也是这样?),客户端通常针对抽象主题编程; +- **代理主题角色(Proxy)**:代理主题角色通过关联关系引用真实主题角色,因此可以控制和操纵真实主题对象;代理主题角色中提供一个与真实主题角色相同的接口(以在需要时代替真实主题角色),同时还可以在调用对真实主题对象的操作之前或之后增加新的服务和功能; +- **真实主题角色(RealSubject)**:真实主题角色是代理角色所代表的真实对象,提供真正的业务操作,客户端可以通过代理主题角色间接地调用真实主题角色中定义的操作。 + +在实际开发过程中,代理模式产生了很多类型: +- 远程代理(Remote Proxy):为一个位于不同地址空间的对象提供一个本地的代理对象。不同的地址空间可以在相同或不同的主机中。 +- 虚拟代理(Virtual Proxy):当创建一个对象需要消耗大量资源时,可以先创建一个消耗较少资源的虚拟代理来表示,当真正需要时再创建。 +- 保护代理(Protect Proxy):给不同的用户提供不同的对象访问权限。 +缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时存储空间,以使更多用户可以共享这些结果。 +- 智能引用代理(Smart Reference Proxy):当一个对象被引用时提供一些额外的操作,比如将对象被调用的次数记录下来等。 + +## 3.代理模式代码实例 +在某应用软件中需要记录业务方法的调用日志,在不修改现有业务的基础上位每个类提供一个日志记录代理类,在代理类中输出日志,例如在业务方法method()调用之前输出“方法method()被调用,调用时间为2019-10-28 07:33:30”,调用之后输出“方法method()”调用成功。在代理类中调用真实业务类的业务方法,使用代理模式设计该日志记录模块的结构。 + +在这个案例中,真实主题角色是真实业务类,在代理类中调用真实主题角色的method()方法。该实例的UML图如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/13.ProxyPattern/1.Picture/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) + +### 3.1.抽象主题角色 +声明抽象方法method(): +``` +// 抽象主题角色 +class Subject +{ +public: + Subject(){} + virtual ~Subject(){} + virtual void method() = 0; +}; +``` +### 3.2.真实主题角色 +实现具体业务方法method(): +``` +// 真实主题角色 +class RealSubject :public Subject +{ +public: + RealSubject(){} + void method(){ + printf("调用业务方法\n"); + } +}; +``` +### 3.3.代理角色和Log类 +``` +// Log类 +class Log +{ +public: + Log(){} + string getTime(){ + time_t t = time(NULL); + char ch[64] = { 0 }; + //年-月-日 时:分:秒 + strftime(ch, sizeof(ch)-1, "%Y-%m-%d %H:%M:%S", localtime(&t)); + return ch; + } +}; + +// 代理类 +class Proxy:public Subject +{ +public: + Proxy(){ + realSubject = new RealSubject(); + log = new Log(); + } + Proxy(const Proxy& o) = delete; + Proxy& operator=(const Proxy&) = delete; + ~Proxy(){ + delete realSubject; + delete log; + realSubject = nullptr; + log = nullptr; + } + void preCallMethod(){ + printf("方法method()被调用,调用时间为%s\n",log->getTime().c_str()); + } + void method(){ + preCallMethod(); + realSubject->method(); + postCallMethod(); + } + void postCallMethod(){ + printf("方法method()调用调用成功!\n"); + } +private: + RealSubject *realSubject; + Log* log; +}; +``` +### 3.4.客户端代码示例 +``` +#include +#include "ProxyPattern.h" + +int main() +{ + Subject *subject; + subject = new Proxy(); + subject->method(); + + printf("\n\n"); + + delete subject; + subject = nullptr; + + system("pause"); + return 0; +} +``` +### 3.5.效果 +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/13.ProxyPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +## 4.总结 +- 优点: + - 代理模式能够协调调用者和被调用者,降低系统耦合度; + - 客户端针对抽象主题角色编程,如果要增加或替换代理类,无需修改源代码,符合开闭原则,系统扩展性好; + - 远程代理优点:为两个位于不同地址空间的对象的访问提供解决方案,可以将一些资源消耗较多的对象移至性能较好的计算机上,提高系统整体性能; + - 虚拟代理优点:通过一个资源消耗较少的对象来代表一个消耗资源较多的对象,节省系统运行开销; + - 缓冲代理优点:为某一个操作结果提供临时的存储空间,可以在后续操作中使用这些结果,缩短了执行时间; + - 保护代理优点::控制对一个对象的访问权限,为不同客户提供不同的访问权限。 +- 缺点: + - 增加了代理类和代理对象,增加了代理对象中的某些处理流程,可能会使得系统响应变慢; + - 有的代理模式(如远程代理)实现代码较为复杂。 +- 适用环境: + - 当客户端对象需要访问远程主机中的对象——可以使用远程代理; + - 当需要用一个资源消耗较少的对象来代表一个资源消耗较多的对象——虚拟代理; + - 当需要限制不同用户对一个独享的访问权限——保护代理; + - 当需要为一个频繁访问的操作结果提供临时存储空间——缓冲代理; + - 当需要为一个对象的访问提供一些额外的操作——智能引用代理。 diff --git a/14.ChainOfResponsibility/ChainOfResponsibility.md b/14.ChainOfResponsibility/ChainOfResponsibility.md new file mode 100644 index 0000000..405d681 --- /dev/null +++ b/14.ChainOfResponsibility/ChainOfResponsibility.md @@ -0,0 +1,250 @@ +# “欲戴王冠,必承其重”——深度解析职责链模式 +``` +应项目需求,公司安排Jungle去成都出差一段时间。这不,Jungle刚结束出差生活,回到公司准备报销。算了一下,Jungle一共有大概50万的一笔小额票据需要报销。按照公司规定,Jungle得先去找自己的组长签字。 +组长一看,“啧啧啧,我只能处理10万金额以下的报销,你这单子我签个字,你还得找兵哥(主管)签字”,于是Jungle又跑去找兵哥。 +兵哥看了,“啧啧啧,我最多只能管金额不超过30万的单子,你得找春总(经理)签字”。Jungle又不厌其烦地找到了春总。 +春总一看,“呵,50万,不少啊!但60万以下我都能做主,这单子我给你签了!要是超过60万,你这狗子还得去找老板!”Jungle总算松了一口气,一级一级网上找领导,也是不容易呀! +``` +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/14.ChainOfResponsibility/1.Picture/%E8%81%8C%E8%B4%A3%E9%93%BE%E6%A8%A1%E5%BC%8F%E4%B8%BE%E4%BE%8B%E5%9B%BE.png) + +在单位,每个领导都有不同的审批权限,不同额度的报销单层层上报,最终到有权处理该金额范围的领导手里,该单子才算审批完成。这是Jungle所在的公司(相信也是大部分公司)的制度。如果要用代码来模拟这种制度,有没有一种模式可以参考呢? + +答案是:有!那就是职责链模式! + +## 1.职责链模式简介 +职责链模式又叫责任链模式。很多情况下,可以处理某个请求的对象可能不止一个,请求可以沿着某一条对象之间形成的关系一级一级由下家传递到上家,形成一条链——职责链。职责链可以是直线,也可以是环或树形结构。常见的职责链形式是直线。**链上的每一个对象都是请求的处理者,客户端要做的仅仅是发送请求,不需要关心请求的处理细节过程。由此,职责链模式将请求者和请求的接收者解耦**。 + +职责链模式定义如下: +``` +职责链模式: +避免将一个请求的发送者和接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。 +``` +## 2.职责链模式结构 +职责链模式的UML结构如下图所示,职责链模式的核心在于引入了一个抽象处理者: +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/14.ChainOfResponsibility/1.Picture/%E8%81%8C%E8%B4%A3%E9%93%BE%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +职责链模式中一共包含两个角色: + +- **Handler(抽象处理者)**:抽象处理者一般为抽象类,声明了一个处理请求的接口handleRequest(),定义了一个抽象处理者类型的对象,作为其对下家的引用,通过该引用可以形成一条责任链。 +- **ConcreteHandler(具体处理者)**: 是抽象处理者的子类,实现了处理请求的接口。在具体的实现中,如果该具体处理者能够处理该请求,就处理它,否则将该请求转发给后继者。具体处理者可以访问下一个对象。 +由上述可知,在职责链模式中很多对象由每一个对象对其下家的引用连接起来形成一条链条,请求在这个链条上逐级传递,知道某一级能够处理这个请求为止。**客户端不知道也不必知道是哪一级处理者处理了该请求,因为每个处理者都有相同的接口handleRequest()**。接下来通过一个实例来进一步认识职责链模式。 + +## 3.职责链模式代码实例 +以引言中的例子为例,对于不同金额的票据,公司不同级别的领导处理情况如下: +``` +金额0~10万:组长可处理 +金额10~30万:主管处理 +金额30~60万:经理处理 +金额超过60万:老板处理 +``` + +本节Jungle将用C++模拟该过程。该实例UML图如下: +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/14.ChainOfResponsibility/1.Picture/%E8%81%8C%E8%B4%A3%E9%93%BE%E6%A8%A1%E5%BC%8FUML%E5%AE%9E%E4%BE%8B%E5%9B%BE.png) + +### 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; +}; +``` +### 3.2.抽象处理者 +``` +// 抽象处理者 +class Approver +{ +public: + Approver(){} + Approver(string iName){ + setName(iName); + } + virtual ~Approver(){} + // 添加上级 + 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; +}; +``` +### 3.3.具体处理者 +#### 3.3.1.具体处理者:组长 +``` +// 具体处理者:组长 +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); + } + } +}; +``` +#### 3.3.2.具体处理者:主管 +``` +// 具体处理者:主管 +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); + } + } +}; +``` +#### 3.3.3.具体处理者:经理 +``` +// 具体处理者:经理 +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); + } + } +}; +``` +#### 3.3.4.具体处理者:老板 +``` +// 具体处理者:老板 +class Boss :public Approver +{ +public: + Boss(){} + Boss(string iName){ + setName(iName); + } + // 处理请求 + void handleRequest(Bill *bill){ + printf("老板 %s 处理了该票据,票据信息:", this->getName().c_str()); + bill->print(); + } +}; +``` +### 3.5.客户端代码示例 +客户端创建了四个角色,分别是组长、主管、经理和老板,并设置了上下级关系。然后创建了4张票据,金额不等,都先统一交给组长处理。 +``` +#include +#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"); + + delete zuzhang; + delete bingge; + delete chunzong; + delete laoban; + delete bill1; + delete bill2; + delete bill3; + delete bill4; + + system("pause"); + return 0; +} +``` +### 3.6.效果 +运行结果如下图,可以看到,针对不同金额的票据,处理请求在不同职级之间层层上报,成功模拟了引言中的过程. + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/14.ChainOfResponsibility/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +## 4.总结 +- 优点: + - 将请求的接收者和处理者解耦,客户端无需知道具体处理者,只针对抽象处理者编程,简化了客户端编程过程,降低系统耦合度; + - 在系统中增加一个新的处理者时,只需要继承抽象处理者,重新实现handleRequest()接口,无需改动原有代码,符合开闭原则; + - 给对象分配职责时,职责链模式赋予系统更多灵活性。 +- 缺点: + - 请求没有一个明确的接收者,有可能遇到请求无法响应的问题; + - 比较长的职责链,其处理过程会很长。 + - 建立职责链的工作是在客户端进行,如果建立不当,可能导致循环调用或者调用失败。 +- 适用环境: + - 有多个对象处理同一个请求,具体由谁来处理是在运行时决定,客户端只需发出请求到职责链上,而无需关心具体是谁来处理; + - 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变职责链中各个处理者之间的上下级关系。 + diff --git a/15.CommandPattern/CommandPattern.md b/15.CommandPattern/CommandPattern.md new file mode 100644 index 0000000..65fc510 --- /dev/null +++ b/15.CommandPattern/CommandPattern.md @@ -0,0 +1,311 @@ +# 作为程序员的你,必须要知道命令模式~ +``` +还记得Jungle曾经设计的Qt图片浏览器吗?鼠标点击“上一张”,浏览上一张图片;点击“下一张”,浏览下一张图片;点击“自动播放”,则自动从上到下播放每一张图片。是不是很有趣的一个小程序? +``` +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/15.CommandPattern/1.Picture/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F.jpg) + +鼠标点击某个键,就好像用户在向图片浏览器发送指令,图片浏览器内部接收到指令后开始调用相应的函数,最终结果是播放上一张或下一张图片,即执行或响应了用户发出的命令。客户并不知道发出的命令是什么形式,也不知道图片浏览器内部命令是如何执行的;同样,浏览器内部也不知道是谁发送了命令。**命令的发送方和接收方(执行方)没有任何关联**。在软件设计模式中,有一种将命令的发送者与执行者解耦的设计模式——命令模式。 +## 1.命令模式简介 +命令模式可以将请求(命令)的发送者与接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道请求是如何完成的。下面是比较晦涩难懂的命令模式的定义: +``` +命令模式: +将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。 +``` +命令模式的定义比较复杂,也提到一些术语。这些将在下面的阐述和举例中做进一步说明。 +## 2.命令模式结构 +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/15.CommandPattern/1.Picture/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +命令模式的UML结构如上图,命令模式一共有以下几种角色: + +- Command(抽象命令类):是一个抽象类,声明了用于执行命令的接口execute()。 +- ConcreteCommand(具体命令类):具体的命令类,实现了执行命令的接口execute(),它对应具体的接收者对象,将接收者(Receiver)的动作action()绑定其中。在execu()方法中将调用接收者的动作action()。(这就是定义中的“将请求封装成一个对象”的体现) +- Invoker(调用者):请求的发送者,通过命令对象来执行请求。一个调用者不需要在设计时确定其接收者,所以调用者通过聚合,与命令类产生关联。具体实现中,可以将一个具体命令对象注入到调用者中,再通过调用具体命令对象的execute()方法,实现简介请求命令执行者(接收者)的操作。 +- Receiver(接收者): 实现处理请求的具体操作(action)。 + +## 3.命令模式代码实例 +房间中的**开关(Button)**就是命令模式的一个实现,本例使用命令模式来模拟开关功能,可控制的对象包括**电灯(Lamp)**和**风扇(Fan)**。用户每次触摸(touch)开关,都可以打开或者关闭电灯或者电扇。 +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/15.CommandPattern/1.Picture/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) + +本实例的UML图如上所示。抽象命令类仅声明execute()接口。有两个具体命令类,分别是控制灯的LampCommand和控制风扇的FanCommand类,两个具体类中实现了execute()接口,即执行开关灯/风扇请求。本例中的调用者是按钮Button,每次用户触摸touch())开关按钮,即是在发送请求。本例具体设计实现过程如下。 + +### 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; +}; +``` +### 3.2.抽象命令类 +``` +// 抽象命令类 Command +class Command +{ +public: + Command(){} + virtual ~Command(){} + // 声明抽象接口:发送命令 + virtual void execute() = 0; +private: + Command *command; +}; +``` +### 3.3.具体命令类 +``` +// 具体命令类 LampCommand +class LampCommand :public Command +{ +public: + LampCommand(){ + printf("开关控制电灯\n"); + lamp = new Lamp(); + } + LampCommand(const LampCommand&) = delete; + LampCommand& operator=(const LampCommand&) = delete; + ~LampCommand(){ + delete lamp; + lamp = nullptr; + } + // 实现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(); + } + FanCommand(const FanCommand& a) = delete; + FanCommand& operator=(const FanCommand&) = delete; + ~FanCommand(){ + delete fan; + fan = nullptr; + } + // 实现execute() + void execute(){ + if (fan->getFanState()){ + fan->off(); + } + else{ + fan->on(); + } + } +private: + Fan *fan; +}; +``` +### 3.4.调用者:Button +``` +// 调用者 Button +class Button +{ +public: + Button(){} + // 注入具体命令类对象 + void setCommand(Command *cmd){ + this->command = cmd; + } + // 发送命令:触摸按钮 + void touch(){ + printf("触摸开关:"); + command->execute(); + } +private: + Command *command; +}; +``` +### 3.5.客户端代码示例 +``` +#include +#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"); + + delete button; + delete lampCmd; + delete fanCmd; + delete button2; + delete lampCmd2; + delete fanCmd2; + + system("pause"); + return 0; +} +``` +### 3.6.效果 +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/15.CommandPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +可以看到,客户端只需要有一个调用者和抽象命令类,在给调用者注入命令时,再将命令类具体化(这也就是定义中“可用不同的请求对客户进行参数化”的体现)。客户端并不知道命令是如何传递和响应,只需发送命令touch()即可,由此实现命令发送者和接收者的解耦。 + +如果系统中增加了新的功能,功能键与新功能对应,只需增加对应的具体命令类,在新的具体命令类中调用新的功能类的action()方法,然后将该具体命令类通过注入的方式加入到调用者,无需修改原有代码,符合开闭原则。 + +## 4.命令队列 +有时候,当请求发送者发送一个请求时,有不止一个请求接收者产生响应(Qt信号槽,一个信号可以连接多个槽),这些请求接收者将逐个执行业务方法,完成对请求的处理,此时可以用命令队列来实现。比如按钮开关同时控制电灯和风扇,这个例子中,请求发送者是按钮开关,有两个接收者产生响应,分别是电灯和风扇。 + +可以参考的命令队列的实现方式是增加一个命令队列类(CommandQueue)来存储多个命令对象,不同命令对象对应不同的命令接收者。调用者也将面对命令队列类编程,增加注入具体命令队列类对象的方法setCommandQueue(CommandQueue *cmdQueue)。 + +下面的例子展示了按钮开关请求时,电灯和风扇同时作为请求的接收者。代码如下所示: +``` +#ifdef COMMAND_QUEUE +/*************************************/ +/* 命令队列 */ +#include + +// 命令队列类 +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: + vectorcommandQueue; + +}; + +// 调用者 +class Button2 +{ +public: + Button2(){} + // 注入具体命令队列类对象 + void setCommandQueue(CommandQueue *cmdQueue){ + this->cmdQueue = cmdQueue; + } + // 发送命令:触摸按钮 + void touch(){ + printf("触摸开关:"); + cmdQueue->execute(); + } +private: + CommandQueue *cmdQueue; +}; + +#endif +``` +客户端代码如下: +``` +#ifdef COMMAND_QUEUE + + printf("\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 +``` +效果如下图: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/15.CommandPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE2.png) + +## 5.命令模式其他应用 +### 5.1.记录请求日志 +将历史请求记录保存在日志里,即请求日志。很多软件系统都提供了日志文件,记录运行过程中的流程。一旦系统发生故障,日志成为了分析问题的关键。日志也可以保存命令队列中的所有命令对象,每执行完一个命令就从日志里删除一个对应的对象。 + +### 5.2.宏命令 +宏命令又叫组合命令,是组合模式和命令模式的结合。宏命令是一个具体命令类,拥有一个命令集合,命令集合中包含了对其他命令对象的引用。**宏命令通常不直接与请求者交互,而是通过它的成员来遍历调用接收者的方法。当调用宏命令的execute()方法时,就遍历执行每一个具体命令对象的execute()方法**。(类似于前面的命令队列) + +## 6.总结 +- 优点: + - 降低系统耦合度,将命令的请求者与接收者分离解耦,请求者和发送者不存在直接关联,各自独立互不影响。 + - 便于扩展:新的命令很容易加入到系统中,且符合开闭原则。 + - 较容易实现命令队列或宏命令。 + - 为请求的撤销和回复操作提供了一种设计实现方案。 +- 缺点: + - 命令模式可能导致系统中有过多的具体命令类,增加了系统中对象的数量。 +- 适用环境: + - 系统需要将请求发送者和接收者解耦,使得发送者和接收者互不影响。 + - 系统需要在不同时间指定请求、将请求排队和执行请求。 + - 系统需要支持命令的撤销和恢复操作。 + - 系统需要将一组操作组合在一起形成宏命令。 \ No newline at end of file diff --git a/16.InterpreterPattern/InterpreterPattern.md b/16.InterpreterPattern/InterpreterPattern.md new file mode 100644 index 0000000..02a4979 --- /dev/null +++ b/16.InterpreterPattern/InterpreterPattern.md @@ -0,0 +1,252 @@ +# 读懂老板的暗语,你需要知道解释器模式~ +``` +看过《大明王朝1566》吗?这是Jungle所看过的历史剧当中最最喜欢和推崇的一部剧。看过这部剧的小伙伴们都知道,嘉靖皇帝说话从来不会明明白白说出来,而是喜欢绕着说,或者说暗语,若不细细揣测,根本不知道嘉靖说的真实含义是什么。比如他跟陈洪说“行到水穷处,坐看云起时”,陈洪就意会到皇上是让他除草;太子喜获儿子,嘉靖给了枣和栗……要是Jungle生活在那时候,脑壳真得变大啊,整天揣测皇帝的意图都够了。要是有个解释器就好了,能够把皇帝的话解释为明明白白的语言! +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/16.InterpreterPattern/1.Picture/%E5%BC%95%E8%A8%80.png) + +``` +## 1.解释器模式概述 +解释器模式用于描述一个简单的语言解释器,主要应用于使用面向对象语言开发的解释器的设计。当需要开发一个新的语言是,可以使用解释器模式。 +``` +解释器模式: +给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 +``` +解释器模式需要解决的是,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构件一个解释器,该解释器通过解释这些句子,来解决该问题。解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。 +## 2.解释器模式结构 +解释器模式的结构由抽象表达式、终结符表达式、非终结符表达式和环境类组成: +- AbstractExpression(抽象表达式):声明了抽象的解释操作interpret(),是所有终结符表达式和非终结符表达式的基类; +- TerminalExpression(终结符表达式):**终结符是文法规则的组成元素中最基本的语言单位,不能再分解**。终结符表达式实现了与文法规则中终结符相关的解释操作,句子中的每一个终结符都是该类的一个实例。 +- NonterminalExpression(非终结符表达式):实现了文法规则中非终结符的解释操作,因为非终结符表达式同样可以包含终结符表达式,所以终结符表达式可以是非终结符表达式的成员。 +- Context(环境类):即上下文类,用于存储解释器之外的一些全局信息,通常临时存储需要解释的语句。 + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/16.InterpreterPattern/1.Picture/%E8%A7%A3%E9%87%8A%E5%99%A8%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png) + +解释器模式的UML图如上所示。抽象表达式声明了抽象接口interpret(),终结符表达式和非终结符表达式式具体实现了该接口。其中,终结符表达式的interpret()接口实现了具体的解释操作,而**非终结符表达式中可能包含终结符表达式或者非终结符表达式,所以非终结符表达式的interpret()接口中可能是递归调用每一个组成部分的interpret()方法**。 + +## 3.解释器模式代码实例 +本节Jungle使用解释器模式实现下面一个小功能: + +设计一个简单的解释器,使得系统可以解释0和1的或运算和与运算(不考虑或运算和与运算的优先级,即从左往右依次运算),语句表达式和输出结果的几个实例如下表: + +|表达式|输出结果| +|:--:|:--:| +|1 and 1|1| +|0 or 0|0| +|1 or 1|1| +|1 or 0|1| +|1 and 0|0| +|0 and 0|0| +|1 and 1 or 0|1| +|0 or 1 and 0|0| +|0 or 1 and 1 or 1|1| +|1 or 0 and 1 and 0 or 0|0| + +结合前面叙述的解释器模式的结构和本例,可以划分出以下角色: + +- 终结符表达式角色——**值节点(ValueNode)**:0、1,因为它们是表达式的基本组成元素,不可再细分 +- 终结符表达式角色——**运算符节点(OperatorNode)**:运算符号“and”和“or” ,同样也是表达式的基本组成元素 +- 非终结符表达式角色——**句子节点(SentenceNode)**:类似于“1 and 1”这样的表达式或者更长的组合表达式 +- 上下文类角色——**处理者(Handler)**:保存输入的表达式和输出的结果 +由此,本例的UML实例图如下: +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/16.InterpreterPattern/1.Picture/%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png) + +### 3.1.抽象表达式 +``` +// 抽象表达式类 +class AbstractNode +{ +public: + AbstractNode(){} + virtual ~AbstractNode(){} + // 声明抽象接口 + virtual char interpret() = 0; +}; +``` +### 3.2.终结符表达式角色——值节点 +``` +// 终结符表达式:ValueNode +class ValueNode :public AbstractNode +{ +public : + ValueNode(){} + ValueNode(int iValue){ + this->value = iValue; + } + // 实现解释操作 + char interpret(){ + return value; + } +private: + int value; +}; +``` +### 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; +}; +``` +### 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; +}; +``` +### 3.5.上下文角色——处理者 +处理者将处理输入的表达式,并解释出表达式最终的结果。 +``` +// 处理者 +class Handler +{ +public: + Handler(){} + void setInput(string iInput){ + this->input = iInput; + } + void handle(){ + AbstractNode *left = nullptr; + AbstractNode *right = nullptr; + AbstractNode *op = nullptr; + AbstractNode *sentence = nullptr; + string iInput = this->input; + vectorinputList; + char* inputCh = const_cast(iInput.c_str()); + char *token = strtok(inputCh, " "); + while (token != nullptr){ + inputList.push_back(token); + token = strtok(nullptr, " "); + } + 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()); + + delete left; + delete right; + delete op; + delete sentence; + left = nullptr; + right = nullptr; + op = nullptr; + sentence = nullptr; + } + 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; +}; +``` +### 3.6.客户端代码示例和结果 +``` +#include +#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"); + delete handler; + system("pause"); + return 0; +} +``` +运行结果如下: + +![avatar](https://github.com/FengJungle/DesignPattern/blob/master/16.InterpreterPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png) + +## 4.总结 +- 优点: + - 易于改变和扩展文法,在解释器中使用类表示语言的文法规则,可以通过继承等机制类改变或扩展文法; + - 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言; + - 如果要增加新的解释表达式,只需增加一个新的终结符表达式或非终结符表达式类,无需修改原有代码,符合开闭原则。 +- 缺点: + - 对于复杂文法难以维护。在解释器模式中每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会大量增加,导致系统难以管理和维护; + - 执行效率低,因为解释器模式中有大量循环和递归调用。 +- 适用环境: + - 一些重复出现的问题可以用一种简单的语言进行表达; + - 一个语言的文法较为简单; + - 不考虑执行效率的问题时可以使用解释器模式。