65.9K
CodeProject 正在变化。 阅读更多。
Home

设计模式常见问题解答第二部分(设计模式培训系列)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (91投票s)

2008 年 8 月 5 日

CPOL

11分钟阅读

viewsIcon

271846

解释器模式、迭代器模式、中介者模式、备忘录模式和观察者模式

 

引言

这是设计模式常见问题解答第 1 部分的延续。在本常见问题解答系列中,我们将涵盖解释器模式、迭代器模式、中介者模式、备忘录模式和观察者设计模式。

如果您尚未阅读我之前的部分,您可以随时从下方阅读

什么是解释器模式?

解释器模式允许我们将语法解释为代码解决方案。好的,这意味着什么?语法被映射到类以获得解决方案。例如,7 – 2 可以映射到“clsMinus”类。简而言之,解释器模式为我们提供了如何编写一个能够读取语法并在代码中执行的解释器的解决方案。例如,下面是一个简单的示例,我们可以提供日期格式语法,解释器会将其转换为代码解决方案并给出所需的输出。

图:- 日期语法


让我们为“日期语法”图中所示的日期格式创建一个解释器。在开始之前,让我们了解解释器模式的不同组件,然后我们将它们映射以创建日期语法。上下文包含数据,逻辑部分包含将上下文转换为可读格式的逻辑。

图:- 上下文和逻辑


让我们了解日期格式中的语法是什么。为了定义任何语法,我们应该首先将语法分解为小的逻辑组件。“语法映射到类”图显示了如何识别不同的组件,然后将其映射到类,这些类将只包含实现语法该部分的逻辑。因此,我们将日期格式分解为四个组件:月份、日期、年份和分隔符。对于所有这四个组件,我们将定义单独的类,这些类将包含“语法映射到类”图中所示的逻辑。因此,我们将为日期格式的各种组件创建不同的类。

图:- 语法映射到类


如前所述,有两种类,一种是包含逻辑的表达式类,另一种是包含数据的上下文类,如“表达式和上下文类”图中所示。我们将所有表达式解析定义在不同的类中,所有这些类都继承自具有“Evaluate”方法的公共接口“ClsAbstractExpression”。“Evaluate”方法接受一个包含数据的上下文类;此方法根据表达式逻辑解析数据。例如,“ClsYearExpression”将“YYYY”替换为年份值,“ClsMonthExpression”将“MM”替换为月份,依此类推。

图:- 解释器类图

图:- 表达式和上下文类


现在我们已经将单独的表达式解析逻辑放在不同的类中,让我们看看客户端将如何使用迭代器逻辑。客户端首先将日期语法格式传递给上下文类。根据日期格式,我们现在开始在集合中添加表达式。因此,如果我们找到“DD”,我们就添加“ClsDayExpression”;如果我们找到“MM”,我们就添加“ClsMonthExpression”,依此类推。最后,我们只需循环并调用“Evaluate”方法。一旦所有评估方法都被调用,我们就显示输出。

图:- 客户端解释器逻辑

 

 

你能解释一下迭代器模式吗?

迭代器模式允许顺序访问元素而无需暴露内部代码。让我们了解这意味着什么。假设你有一个记录集合,你希望按顺序浏览并维护当前浏览的记录集位置,那么答案就是迭代器模式。它是最常见且不知不觉中使用的模式。每当你使用“foreach”(它允许我们按顺序循环遍历集合)循环时,你已经在某种程度上使用了迭代器模式。

图:- 迭代器业务逻辑


在“迭代器业务逻辑”图中,我们有一个“clsIterator”类,其中包含客户类的集合。因此,我们在“clsIterator”类中定义了一个数组列表,以及一个用数据加载数组列表的“FillObjects”方法。客户集合数组列表是私有的,可以通过使用数组列表的索引查找客户数据。因此,我们有公共函数,如“getByIndex”(可以使用特定索引查找)、“Prev”(获取集合中上一个客户)、“Next”(获取集合中下一个客户)、“getFirst”(获取集合中第一个客户)和“getLast”(获取集合中最后一个客户)。

因此,客户端只暴露这些函数。这些函数负责顺序访问集合,并且还记住访问了哪个索引。

下图“客户端迭代器逻辑”显示了如何使用由“clsIterator”类创建的“ObjIterator”对象来按索引显示下一个、上一个、最后一个、第一个和客户。

图:- 客户端迭代器逻辑

 

 

你能解释一下中介者模式吗?


在项目中,组件之间的通信往往很复杂。这导致组件之间的逻辑变得非常复杂。中介者模式帮助对象以非关联的方式通信,从而最大限度地降低复杂性。

图:- 中介者模式示例


让我们看一下“中介者模式示例”图,它描绘了对中介者模式需求的真实场景。这是一个非常用户友好的用户界面。它有三个典型场景。
场景 1:- 当用户在文本框中输入时,它应该启用添加和清除按钮。如果文本框中没有内容,它应该禁用添加和清除按钮。

图:- 场景 1


场景 2:- 当用户点击添加按钮时,数据应该输入到列表框中。数据输入到列表框后,它应该清除文本框并禁用添加和清除按钮。

图:- 场景 2


场景 3:- 如果用户点击清除按钮,它应该清除名称文本框并禁用添加和清除按钮。

图:- 场景 3


现在,根据上述 UI 场景,我们可以得出这些 UI 之间交互的复杂程度。下图“组件之间复杂的交互”描绘了逻辑复杂性。

图:- 组件之间复杂的交互


好的,现在让我给你一个漂亮的图片,如下图“使用中介者简化”。与其让组件直接相互通信,不如让它们与像中介者这样的集中式组件通信,然后中介者负责将这些消息发送给其他组件,逻辑会变得整洁清晰。

图:- 使用中介者简化

现在让我们看看代码会是什么样子。我们将使用 C#,但你可以轻松地将思想复制到 JAVA 或你选择的任何其他语言。下图“中介者类”显示了中介者类的完整代码概述。


中介者类做的第一件事是获取具有复杂通信的类的引用。因此,我们在这里公开了三个名为“Register”的重载方法。“Register”方法接受文本框对象和按钮对象。交互场景集中在“ClickAddButton”、“TextChange”和“ClickClearButton”方法中。这些方法将根据场景负责 UI 组件的启用和禁用。

图:- 中介者类


现在客户端逻辑非常整洁和酷。在构造函数中,我们首先将所有具有复杂交互的组件注册到中介者。现在对于每个场景,我们只需调用中介者方法。简而言之,当文本发生更改时,我们可以调用中介者的“TextChange”方法;当用户单击添加时,我们调用“ClickAddButton”;对于清除单击,我们调用“ClickClearButton”。

图:- 中介者客户端逻辑

 

你能解释一下备忘录模式吗?


备忘录模式是在不违反封装的情况下捕获对象内部状态的方法。备忘录模式帮助我们存储一个快照,对象可以在任何时候恢复到该快照。让我们从实际意义上理解这意味着什么。考虑“备忘录实际示例”图,它显示了一个客户屏幕。假设用户开始编辑客户记录并进行了一些更改。后来他觉得他做错了什么,他想恢复到原始数据。这就是备忘录发挥作用的地方。它将帮助我们存储数据副本,如果用户按下取消,对象将恢复到其原始状态。

图:- 备忘录实际示例


让我们尝试为我们刚刚介绍的客户 UI 在 C# 中完成相同的示例。下面是客户类“clsCustomer”,它聚合了备忘录类“clsCustomerMemento”,该类将保存数据的快照。备忘录类“clsCustomerMemento”是客户类“clsCustomer”的精确副本(不包括方法)。当客户类“clsCustomer”初始化时,备忘录类也初始化。当客户类数据更改时,备忘录类快照不变。“Revert”方法将备忘录数据设置回主类。

图:- 备忘录的客户类

客户端代码非常简单。我们创建客户类。如果出现问题,我们点击取消按钮,它会调用“revert”方法,并将更改的数据恢复到备忘录快照数据。图“备忘录客户端代码”以图片形式显示了这一点。

图:- 备忘录客户端代码

你能解释一下观察者模式吗?


观察者模式帮助我们实现父类与其关联或依赖类之间的通信。观察者模式中有两个重要概念:“主题”和“观察者”。主题发送通知,而观察者在注册到主题后接收通知。下图“主题和观察者”展示了应用程序(主题)如何向所有观察者(电子邮件、事件日志和短信)发送通知。你可以将此示例映射到发布者和订阅者模型。发布者是应用程序,订阅者是电子邮件、事件日志和短信。

图:- 主题和观察者


让我们尝试编写与上一节中定义的相同示例的代码。首先让我们看看订阅者/通知类。“订阅者类”图以图片形式显示了这一点。因此,我们所有订阅者都有一个公共接口,即“INotification”,它有一个“notify”方法。所有具体的通知类都实现了这个接口“INotification”。所有具体的通知类都定义了自己的通知方法。对于当前场景,我们只是显示一条打印信息,说明特定的通知已执行。

图:- 订阅者类


如前所述,观察者模式有两个部分,一个是我们上一节介绍的观察者/订阅者,第二个是发布者或主题。

发布者有一个数组列表集合,其中将添加所有对接收通知感兴趣的订阅者。使用“addNotification”和“removeNotification”我们可以从数组列表中添加和删除订阅者。“NotifyAll”方法循环遍历所有订阅者并发送通知。

图:- 发布者/主题类


现在我们对发布者和订阅者类有了概念,让我们编写客户端代码并看看观察者模式的实际应用。下面是观察者客户端代码片段。所以我们首先创建通知器对象,它包含订阅者对象集合。我们将所有需要通知的订阅者添加到集合中。
现在,如果客户代码长度超过 10 个字符,则通知所有订阅者。

图:- 观察者客户端代码

 

如果您完全不熟悉设计模式,或者您真的不想阅读这篇完整的文章,请观看我们的免费设计模式培训和面试问答视频。

带项目的设​​计模式

学习设计模式的最佳方式是做一个项目。因此,本教程是逐个模式地教授您,但如果您想通过项目方法学习设计模式,请点击此链接

如需进一步阅读,请观看以下面试准备和分步教程视频:

访问我的个人资料以获取更多视频。

© . All rights reserved.