这是设计模式问答1的连载。在这个系列,我们将覆盖到解释器、迭代器、调停者、备忘录和观察者模式。
如果你还没有阅读过我之前的系列,你可以随时从下面开始
解释器模式允许我们将语法解释为代码解决方案。好了,这意味着什么?语法会被映射到类,并形成解决方案。 举个例子,7 – 2能够被映射到“clsMinus”类。一句话,解释器模式为我们提供了一种解决方案,这个方案指导如何编写一个解析语法并执行代码的解释器。下面是一个 解释器的简单例子,它能够按照我们提供的日期格式语法,将日期解释为对应的代码,并输入正确的结果。
让我们开始做图“日期语法”中所示的日期格式的解释器。在开始之前,我们要先理解一下解释器模式中不同的组件,然后再来处理映射。上下文部分包含数据,而逻辑部分包含将上下文中数据转换成可读格式的转换逻辑。
让我们看一下日期格式的语法是怎么定义的。定义任何语法的第一步,是把语法分解成小的逻辑组件。图“语法映射与类 的映射”展示了怎么识别这些组件,以及怎么映射到处理这部分语法的逻辑类上面。我们已经把日期格式打断成了4个组件,分别是月、日、年和分隔符。对这4个 组件,我们将分别定义包含图中展示的逻辑的类。然后,我们将为日期格式的不同组件创建不同的类。
前 面说过,有两种类,一种是包含逻辑的表达式类,另一种是包含数据的上下文类,如图“表达式和上下文类”中所示。我们定义了不同类中的表达式解析算法,这些 类都从公共接口“ClsAbstractExpression”派生,并实现了“Evaluate”方法。“Evaluate”方法接收包含数据的上下文 类作为参数;它根据表达式逻辑来解析数据。“ClsYearExpression”实例将“YYYY”替换成年份值,而 “ClsMonthExpression”将“MM”替换成月份值,以此类推。
现在,我们有了单独的表达式解析逻辑类,然后我们来看看客户端会如何使用这个逻辑。客户端首先把日期语法格式传递给 上下文类。依据日期格式,我们依次向集合中添加表达式实例。如果我们找到了“DD”,我们就添加一个“ClsDayExpression”实例;如果我们 找到了“MM”,就添加一个“ClsMonthExpression”实例,等等。最后,我们只需要遍历集合,并调用“Evaluate”函数。所有的 “Evaluate”函数执行完之后,我们就显示结果。
迭代器模式允许在不暴露内部代码实现的情况下,顺序访问每个元素。让我们来理解一下。假设你有一个记录集合,你需要顺序遍历 每条记录,并且需要保持当前访问的位置,那么你需要的正是迭代器模式。这是最普通的设计模式,你会在不知不觉中用到它。在某些程度上,当你使用 “foreach”(它允许我们逐个元素地访问一个集合)时,你就已经在使用迭代器模式了。
在图“迭代器业务逻辑”中,我们使用 “clsIterator”类来存放顾客类的集合。我们在“clsIterator”类内部,定义了一个数组,和一个名称为“FillObjects”的 方法,这个方法用来加载数组的内容。顾客集合数组是私有的,顾客的数据可以通过数组的下标来访问。因此我们定义了一组公有函数,包含 “getByIndex”(通过指定下表来访问),“Prev”(得到集合中前一个顾客数据),“Next”(得到集合中下一个顾客数 据),“getFirst”(得到集合中第一个顾客数据),“getLast”(得到集合中最后一个顾客数据)。
我们只对客户端暴露这些函数。这些函数小心的顺序遍历集合,并能够记忆当前遍历的索引。
下面的图“客户遍历逻辑”说明了该如何使用类“clsIterator”的实例“ObjIterator”,显示下一个、前一个、最后一个、第一个,以及通过索引显示顾客数据。
大多数时候,项目中组件间的通讯都很复杂。因此组件间的逻辑关系也变得异常复杂。调停者模式帮助对象间用不互相关联的方式来通讯,从而使复杂度最小化。
让我们考虑图“调停者模式示例”,它描述了一个需要使用调停者模式的真实场景。它是一个非常用户友好的接口。它有3个典型的场景。
场景1:当用户在文本框中输入时,应该使添加和清除按钮可用。一旦当文本框中没有文字时,应该禁用添加和清除按钮。
场景2:当用户点击添加按钮时,文本框内的数据应该被输入到列表框内。一旦数据被输入到列表框,它应该清空文本框的内容,并禁用添加和清除按钮。
场景3:- 当用户点击清除按钮时,名字文本框内的内容被清空,并禁用添加和清除按钮。
现在从界面上来看上面几个场景,我们可以推断这些界面之间的交互是多么复杂。下图“组件间复杂交互”显示出了逻辑复杂性。
好了,让我给你们看一个好看的图,即下图“通过调停者简化”。与其组件之间直接通讯,不如通过一个作为调停者的中心组件通讯,调停者组件管理着发送给其他组件的消息,这样逻辑更加优雅和清晰。
现在我们来看看代码会是什么样子。我们将使用C#,但是你可以很轻松的把这种思想应用在Java或其他语言上。下图“调停者类”展示了一个调停者类完整的代码概述。
调停者类做的第一件事,是保存拥有复杂通讯的类的引用。因此,我们 对外暴露了3个重载的方法“Register”。“Register”方法接收文本框对象和按钮对象为参数。交互场景集中在 “ClickAddButton”,“TextChange”和“ClickClearButton”三个方法上。将根据场景不同,这些方法将管理UI组 件的可用与禁用。
现在的客户逻辑非常优雅、非常酷。在构造函数中,我们首先将 参与复杂通讯的所有组件注册到调停者对象中。然后在每个场景中,我们只需要调用调停者对象的函数。简单地说,当有文本变化时,我们调用调停者对象的 “TextChange”函数;当用户点击添加按钮时,我们调用“ClickAddButton”;当点击清除按钮时,调用 “ClickClearButton”函数。
备忘录模式能够在不破坏封装原则的前提下,获取对象内部状态。备忘录模式帮助我们存储一个对象的快照,它可 以在任意时间被恢复。让我们通过实例来理解。考虑图“备忘录示例”,它展示了一个顾客的界面。假设用户开始编辑一条顾客记录,并做了一些修改。然后用户觉 得有错误,希望能够恢复到原始的数据。这时备忘录模式就登场了。它帮助我们存储数据的一个备份,并且当用户点击“取消”按钮时,对象能够恢复到它的原始状 态。
让我们尝试用C#来实现刚才所讲的顾客界面。下图是顾客类 “clsCustomer”,它聚合了一个备忘录类“clsCustomerMemento”。备忘录类将保存数据的快照,它是顾客类 “clsCustomer”的精确的复制品(除了方法)。当顾客类“clsCustomer”初始化时,备忘录类也将被初始化。当顾客类数据变化时,备忘 录类的快照不变化。“Revert”函数把备忘录的数据写回到主类。
客户端的代码相当简单。我们创建一个顾客类。一旦遇到问题,我们点击“取消”按钮,调用“Revert”函数,将修改过的数据恢复到备忘录快照的原始数据。图“备忘录客户端代码”形象地展示了这个过程。
观察者模式帮助我们与父类,关联类或者依赖类之间进行通讯。观察者模式中,有两个重要的概念,分别是“主体”和“观察者”。主体发 送通知,如果观察者已经注册到主体的话,观察者会收到通知。下图“主体和观察者”展示了应用程序(主体)是如何给所有观察者(邮件,事件日志,短消息服 务)发送通知的。你可以把这个例子对应到发布者与订阅者模型。发布者就是应用程序,而订阅者是邮件,事件日志和短消息服务。
让我们尝试对前面定义的示例进行编码。 首先我们看一下订阅者/通知者类。图“订阅者类”做了一个直观地展示。对所有的订阅者,我们有一个公共的接口,“INotification”,它有一个 “notify”方法。所有需要接收通知的类,都需要实现这个“INotification”接口。所有需要接收通知的类,定义各自的响应方法。对当前场 景,我们只打印一个消息,表明特定的通知被执行了。
前面说过,观察者模式中,有两个部分,一个是我们前面说过的观察者/订阅者,另一个就是发布者,或者叫主体。
发布者有一个所有对接收通知感兴趣的订阅者的集合列表。通过“addNotification”和“removeNotification”,我们可以在列表中增加或者删除订阅者。“NotifyAll”方法遍历所有的订阅者,并发送通知。
现在,我们已经有了发布者和订阅者类。我们来动手编写一下客户端代码。下面是观察者模式客户端的代码片段。首先我们创建一个拥有订阅者集合的通知者对象。然后我们向集合中添加需要被通知的订阅者。
现在,如果客户端输入的顾客代码超过10个字符,就需要通知所有的订阅者。
如果你没有学习过设计模式,或者不愿完全阅读本文,请收看我们的免费视频设计模式培训和问答。
本文,以及相关的代码和文件,通过 The Code Project Open License (CPOL) 协议授权。
原文链接: codeproject 翻译: ImportNew.com - shenggordon
译文链接: http://www.importnew.com/14347.html