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

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.69/5 (64投票s)

2008年8月6日

CPOL

10分钟阅读

viewsIcon

212157

设计模式常见问题解答 第三部分 状态模式、策略模式、访问者模式、适配器模式和享元模式

引言

这篇常见问题解答文章是设计模式常见问题解答第一部分和第二部分的延续。本文中我们将尝试理解状态模式、策略模式、访问者模式、适配器模式和享元模式。

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


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

 

 

你能解释一下状态模式吗?

 

状态模式允许对象根据其当前值改变其行为。请看图“状态模式示例”。这是一个灯泡操作的示例。如果灯泡的状态是关闭的,你按下开关,灯泡就会打开。如果灯泡的状态是打开的,你按下开关,灯泡就会关闭。简而言之,行为会根据状态改变。


图:- 状态模式示例

现在让我们尝试在 C# 中实现相同的灯泡示例。图“状态模式实际应用”展示了类和客户端代码。我们创建了一个名为“clsState”的类,它有一个带有两个状态常量“On”和“Off”的枚举。我们定义了一个方法“PressSwitch”,它根据当前状态切换其状态。在同一图的右侧,我们定义了一个客户端,它使用“clsState”类并调用“PressSwitch()”方法。我们使用“getStatus”函数在文本框上显示当前状态。

当我们点击开关时,它会切换到与我们当前状态相反的状态。

图:- 状态模式实际应用

你能解释一下策略模式吗?


策略模式是类内部的算法,可以根据所使用的类进行互换。当您想在运行时决定使用哪种算法时,此模式很有用。

让我们看一个策略模式实际如何工作的例子。我们以数学计算为例,其中有加法和减法等策略。图“策略模式实际应用”以图形形式展示了这一点。它接受两个数字,并根据策略给出结果。因此,如果它是加法策略,它将把数字相加;如果它是减法策略,它将给出减法结果。这些策略只不过是算法。策略模式只不过是将算法封装在类中。

图:- 策略模式实际应用


所以我们需要关注的第一件事是如何将这些算法封装在类中。下图“算法封装”展示了“加法”如何封装在“clsAddStatergy”类中,“减法”如何封装在“clsSubstractStatergy”类中。这两个类都继承自“clsStratergy”,为其子类定义了一个“calculate”方法。

图:- 封装的算法


现在我们定义一个名为“clsMaths”的包装器类,它引用了“clsStatergy”类。这个类有一个“setStatergy”方法,用于设置要使用的策略。

图:- 策略和包装器类


下图“策略客户端代码”展示了如何使用包装器类,以及如何使用“setStatergy”方法在运行时设置策略对象。

图:- 策略客户端代码

你能解释一下访问者模式吗?


访问者模式允许我们改变类结构而不改变实际的类。它是一种将逻辑和算法与当前数据结构分离的方法。因此,您可以向当前数据结构添加新的逻辑而无需修改结构。其次,您可以修改结构而无需触及逻辑。

考虑下图“逻辑和数据结构”,我们有一个客户数据结构。每个客户对象都有多个地址对象,每个地址对象都有多个电话对象。此数据结构需要以两种不同的格式显示,一种是简单字符串,另一种是 XML。因此我们编写了两个类,一个是字符串逻辑类,另一个是 XML 逻辑类。这两个类遍历对象结构并给出相应的输出。简而言之,访问者包含逻辑。

图:- 逻辑和数据结构


让我们以上述客户示例为例,尝试在 C# 中实现它。如果您来自其他编程语言,您应该能够相应地进行映射。我们创建了两个访问者类,一个用于解析字符串逻辑,另一个用于 XML。这两个类都有一个访问方法,该方法接受每个对象并相应地解析它们。为了保持一致性,我们让它们实现了公共接口“IVisitor”。

图:- 访问者类



上面定义的访问者类将被传递给数据结构类,即客户类。因此,在客户类中,我们将访问者类传递给一个“Accept”函数。在同一个函数中,我们传递此类的类型并调用访问函数。访问函数是重载的,因此它将根据传递的类类型进行调用。

图:- 传递给数据结构类的访问者

现在,每个客户都有多个地址对象,每个地址都有多个电话对象。因此,我们在“clsCustomer”类中聚合了“objAddresses”ArrayList 对象,并在“clsAddress”类中聚合了“objPhones”ArrayList 对象。每个对象都有一个接受方法,该方法接受访问者类,并在访问者类的 visit 函数中传递自身。由于访问者类的 visit 函数是重载的,它将根据多态性调用适当的访问者方法。

图:- 客户、地址和电话


既然逻辑在访问者类中,数据结构在客户类中,那么是时候在客户端中使用它们了。
图“访问者客户端代码”显示了使用访问者模式的示例代码片段。因此,我们创建访问者对象并将其传递给客户数据类。如果我们要以字符串格式显示客户对象结构,我们创建“clsVisitorString”;如果我们要生成 XML 格式,我们创建“clsXML”对象并将其传递给客户对象数据结构。您可以轻松看到逻辑现在是如何从数据结构中分离出来的。

图:- 访问者客户端代码

 

访问者模式和策略模式有什么区别?


访问者模式和策略模式看起来非常相似,因为它们都涉及将复杂逻辑从数据中封装起来。我们可以说访问者模式是策略模式更一般的形式。
在策略模式中,我们有一个上下文或单个逻辑数据,多个算法对其进行操作。在前面的问题中,我们已经解释了策略模式和访问者模式的基本原理。因此,让我们通过之前理解的例子来理解它们。在策略模式中,我们有一个数据上下文,多个算法在其上工作。图“策略模式”展示了我们如何拥有一个数据上下文以及多个算法在其上工作。

图:- 策略模式


在访问者模式中,我们有多个上下文,并且每个上下文都有一个算法。如果你还记得访问者示例,我们为每个数据上下文(即客户、地址和电话对象)编写了解析逻辑。

图:- 访问者模式


简而言之,策略模式是一种特殊的访问者模式。在策略模式中,我们有一个数据上下文和多个算法,而在访问者模式中,对于每个数据上下文,我们都有一个相关的算法。选择实现策略模式还是访问者模式的基本标准取决于上下文和算法之间的关系。如果有一个上下文和多个算法,那么我们选择策略模式。如果T有多个上下文和多个算法,那么我们实现访问者算法。

你能解释一下适配器模式吗?


很多时候,由于接口不兼容,两个类会不兼容。适配器帮助我们包装现有类,使它们相互兼容。考虑下图“不兼容的接口”,它们都是用于存储字符串值的集合。它们都有一个方法可以帮助我们将字符串添加到集合中。其中一个方法名为“Add”,另一个名为“Push”。其中一个使用集合对象,另一个使用栈。我们希望使栈对象与集合对象兼容。

图:- 不兼容的接口


有两种实现适配器模式的方法:一种是使用聚合(这被称为对象适配器模式),另一种是使用继承(这被称为类适配器模式)。首先,让我们尝试介绍对象适配器模式。

图“对象适配器模式”展示了如何实现此目标的广阔视图。我们引入了一个新的包装器类“clsCollectionAdapter”,它包装在“clsStack”类的顶部,并将“push”方法聚合到一个新的“Add”方法中,从而使这两个类兼容。

图:- 对象适配器模式

实现适配器模式的另一种方法是使用继承,也称为类适配器模式。图“类适配器模式”展示了我们如何将“clsStack”类继承到“clsCollectionAdapter”中,并使其与“clsCollection”类兼容。

图:- 类适配器模式

什么是享元模式?


享元模式在我们需要创建许多对象且所有这些对象共享某种共同数据时很有用。考虑图“对象和共同数据”。我们需要为组织中的所有员工打印名片。因此我们有两部分数据,一部分是可变数据,即员工姓名,另一部分是静态数据,即地址。我们可以通过只保留一份静态数据的副本并在所有可变数据对象中引用相同的数据来最小化内存。因此我们创建不同副本的可变数据,但引用同一副本的静态数据。这样我们可以最佳地使用内存。

图:- 对象和共同数据


下面是一个 C# 代码示例,演示了如何实际实现享元模式。我们有两个类,“clsVariableAddress”包含可变数据,第二个“clsAddress”包含静态数据。为了确保我们只有一个“clsAddress”实例,我们创建了一个包装器类“clsStatic”,并创建了一个“clsAddress”类的静态实例。这个对象聚合在“clsVariableAddress”类中。

图:- 享元模式的类视图


图“享元客户端代码”显示我们创建了两个“clsVariableAddress”类的对象,但内部的静态数据,即地址,只引用了一个实例。

图:- 享元客户端代码

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

查看工厂设计模式视频 点击此处 

带项目的设​​计模式

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

进一步阅读,请观看下面的面试准备视频和分步视频系列。

© . All rights reserved.