什么是解释器模式?
解释器模式允许将语义解释到代码解决方案中。下面让我们来了解具体含义?语法是映射到类中来应用到解决方案中,例如7-2可以映射到‘clsMinus’类。在第一行解释器模式给我们提供了一种如何编写解释器的方案,该方案中解释器可以读取并在代码中执行该语法。例如以下例子中我们给出了一个日期格式化语法,其中解释器给出了转换的代码解决方案并输出期望结果。
图1:日期语法
让我们来写一个如图“日期语法”中所示的日期格式化解释器。在开始之前我们需要理解解释器模式中不同组件,之后我们将映射相同的语法。Context包含数据,逻辑部分包含将context转换为可读格式的逻辑。
图2:上下文及逻辑
接下来我们看看日期格式化中的语法。在定义任何语法之前,我们首先要把语法拆分为小的逻辑块。图“语法映射到类”展示了不同组件是如何确定的,然后映射到类的语法只是实现了完整语法中的一部分。这样我们将日期格式化拆分为4个组件月、日、年以及分隔符;对这四个组件我们定义了不同的类来包含各自的逻辑,如图“语法映射到类”中所示。因此,我们将为日期格式化中4个不同的组件创建不同的类。
图3:语法映射到类
如前所述,有两个类其中一个是表达式类,该类包含逻辑;另一个是上下文类(context),该类包含数据,如图“表达式和上下文类”所示。我们将不同的解析定义在不同的类中,所有这些类都继承自‘ClsAbstractExpression’接口,该接口包含‘Evaluate’方法;其中‘Evaluate’方法持有一个包含有数据的上下文类,并且通过表达式逻辑来解析数据。例如‘ClsYearExpression’会将‘YYYY’替换为年份,’ClsMonthExpression’将‘MM’替换为月份等等。
图4:解释器模式类图
图5:表达式和上下文类
至此我们已经将表达式解析逻辑封装在了不同的类中,现在我们来看下客户端使用逻辑。客户端首先传递日期格式化语法到上下文类;根据日期格式化我们添加表示类到一个集合中。例如当发现“DD”表达式时会增加‘ClsDayExpression’,遇到‘MM’表达式时则会添加‘ClsMonthExpression’类,等等。最终我们循环调用这些类的‘Evaluate’方法。当所有类都被调用后我们就得到了显示结果。
图6:客户端效用解释器代码逻辑
什么是迭代器模式?
迭代器模式允许在不暴露内部代码的情况下顺序存取元素。具体含义如下。假设有一个记录的集合,我们想顺序性浏览这些结合同时还想保存当前已经浏览的记录的位置,那么迭代器模式就是我们所需要的。该模式是最常见且经常无意中使用的模式。在所有使用‘foreach’(允许顺序性循环一个集合)循环的地方我们就在一定程度上在使用迭代器模式了。
图7:迭代器模式业务逻辑
在图“迭代器模式业务逻辑”中我们定义了一个包含客户类集合的类‘clsIterator’。因此我们我们首先在‘clsIterator’中定义了一个ArrayList以及一个‘FillObjects’方法来为ArrayList填充数据。这个消费者集合ArrayList是私有的(private),并且客户数据可以通过集合中的下标来查找。因此我们开放了‘getByIndex’方法(通过指定下标来查找元素)、‘Prev’方法(获得集合中前一个元素)、‘Next’方法(获取集合中下一个元素)、‘getFirst’方法(获取结合中第一个元素)、‘getLast’方法(获取集合中最后一个元素)。
客户端只能看到这些方法,通过这些函数来顺序访问集合并且可以记住已经访问过的下标。
下图“客户端使用迭代器模式逻辑”展示了如何使用‘clsIterator’中next、previous、last、first函数以及指定下标来创建‘ObjIterator’对象的。
图8:客户端使用迭代器模式逻辑
什么是调停者模式?
很多时候项目中组件之间的通信是非常复杂的,因为组件之间的相互调用而变的复杂。调停者模式帮助对象以没有关联的方式来通信,这使得复杂度最小化。
图9:调停者模式简单例子
让我们考虑上图“调停者模式简单例子”中的情况,该图描述了一个真实场景中需要调停者模式的情况。它是一个非常用户友好的接口;它有3中典型场景:
场景1:当用户在文本框中输入文字后需要开启增加按钮以及清空按钮。在这个例子中文本框中为空,此时需要禁用增加和清空按钮。
图10:场景1
场景2:当用户点击添加按钮时数据应该进入到列表框中。一旦输入内容进入列表框,系统需要清空文本框并禁用增加和清除按钮。
图11:场景2
场景3:如果用户点击清除按钮系统应该清空文本框并禁用增加和清除按钮。
图12:场景3
从上图场景3中UI界面可以看出这些UI之间的交互是多么复杂。下图“组件之间的复杂交互”展示了逻辑的复杂性。
图13:组件之间的复杂交互
接着我们来看下图“调停者使用简化图”。各个组件之间并不是直接交互,而是都与一个中心化组件如调停者交互,由调停者来关注发送信息至其他组件,这样逻辑将变的干净清晰。
图14:调停者使用简化图
下面我们看看代码是如何写的。我们将使用C#但是大家也可以很容易的将其转换为java代码或者其他语言。下图“调停者类”展示的调停者类的完整代码。
调停者类首先做的事情就是持有复杂交互相关类的引用。所以这里我们开放3个名为‘Register’的重载方法;‘Register’持有文本框对象和按钮对象;场景的交互被集中在‘ClickAddButton’、’TextChange’、‘ClickClearButton’方法中,这些方法将根据不同场景启用和禁用相关UI。
图15:调停者类
同时客户端代码非常整洁。在构造函数中首先注册所有需要和调停者类交互的组件;接着对于每个场景只需要调用调停者类的方法即可。简而言之,当文本发生变化时我们调用调停者类的‘TextChange’方法,当用户点击添加按钮时调用‘ClickAddButton’方法,当用户点击清空按钮时调用‘ClickClearButton’方法。
图16:调停者模式客户端逻辑
什么是备忘录模式?
备忘录模式是提供一种在不破坏封装的情况下提供捕获对象内部状态的方式。它有助于存储对象的快照以便任何时候查询。下面我们来了解它在实际场景中的含义。下图“备忘录模式实践例子”中展示的了一个客户屏幕;假设用户开始编辑一条客户记录并作出一些修改;过了一会以后该用户觉得操作有误想要恢复到原始数据,这就是备忘录模式的适用场景。它帮助我们存储一份数据的拷贝并在用户点击取消时恢复到原始状态。
图17:备忘录模式实践例子
下面我们来尝试使用C#完成我们刚刚讨论的客户UI的例子。以下是客户类‘clsCustomer’,该类聚合了存储数据快照的备忘录类‘clsCustomerMemento’。其中备忘录类‘clsCustomerMemento’是明确用来复制客户类(不包括方法)‘clsCustomer’;当消费者类‘clsCustomer’初始化时,备忘录类也跟着初始化;当客户类数据发生改变时,备忘录类中的快照未发生变化;其中‘Revert’方法可以将备忘录中的数据恢复到main类中。
图18:备忘录中的客户类
客户端代码非常简单。创建客户类,一旦有问题我们可以点击取消按钮来调用‘revert’方法将数据恢复至备忘录中的快照数据。图“备忘录客户端代码”图中展示了该逻辑。
图19:备忘录客户端代码
什么是观察者模式?
观察者模式帮助我们完成父类和与他相关或依赖的类之间的通信。其中有两个比较重要的概念‘Subject’(主题)和‘Observers’(观察者)。主题(subject)发送通知后,如果观察者注册到该主题,则会收到通知。下图“subject和observers”展示了应用(主题)发送通知到所有观察者(email、事件日志、SMS)。我们也可以将这个例子封装成发布/订阅模型;发布者就是应用,订阅者就是email、事件日志、sms。
图20:subject和observers
接下来我们尝试我们在前一小节中定义的简单示例代码。首先,我们先看看订阅者/通知类。图“订阅者类”展示了该代码,其中为所有订阅者定义了统一接口如‘INotification’,该接口有一个‘notify’方法。这个接口被所有具体的通知类实现,所有通知类定义自己的通知方法。对当前的场景来说我们只是显示一个打印字符串来表示某个特定方法被执行。
图21:订阅者类
如前所示,观察者模式中分为两个部分,一个是观察者/订阅者,如前边我们提到的一样;另一个是发布者或者主题。
发布者有一个集合来存放所有需要收到通知的订阅者。通过使用‘addNotification’和‘removeNotification’可以从集合中增加或删除订阅者;使用‘NotifyAll’方法循环所有订阅者来发送通知。
图22:发布者/主题类
至此我们已经了解了发布致和订阅者类,下面让我们来些客户端代码并看看观察者表现。下面是一个观察者客户机代码片段。首先创建一个拥有订阅者集合的通知者对象,接着将需要被通知的订阅者添加到结合中。此时的逻辑是如果客户的代码长度超过10个字符时通知所有订阅者。
图24:观察者模式客户端代码
1. 本文由程序员学架构翻译
2. 本文译自
http://www.codeproject.com/Articles/28359/Design-pattern-FAQ-Part-Design-pattern-training
3. 转载请务必注明本文出自:程序员学架构(微信号:archleaner)