设计模式问答(3)

简介

这篇文章是设计模式问答系列(1)和(2)的延续。在这篇文章里,我们将会介绍状态模式,策略模式,访问者模式,适配器模式和享元模式。

如果你完全不了解设计模式或者你其实并不想通读这篇文章,你可以在这里看我们免费的视频 design pattern Training and interview questions / answers 

如果你还没有读过我前边的系列,你可以通过下面的链接阅读:

  1. 设计模式问答(1):工程模式,抽象工程模式,构造器模式,原型模式,单例模式和命令模式
  2. 设计模式问答(2):解释器模式,迭代器模式,调停者模式,备忘录模式和观察者模式
  3. 设计模式问答(4):桥接模式、组合模式、外观模式、职责链模式、代理模式以及模板模式

你能解释下状态模式吗?

状态模式允许一个对象根据对象的当前值改变自己的行为。参考下面的图片-“策略模式的例子”。这是一个开关操作的例子。如果灯泡的是关闭的状态,当你按下开关,灯泡会打开。如果灯泡是打开的状态,当你按下开关,灯泡将会关闭。简而言之,状态模式就是根据状态改变行为。

Figure:-策略模式的例子

现在让我们用C#来实现这个灯泡例子。图片“策略模式正在进行”同时显示了类和客户端的代码。我们创建一个叫‘clsState’的类,它包含一个enum类型其有‘on’和‘off’两种状态常量。我们定义了一个方法‘PressSwitch’,它会根据当前的状态切换自己的状态。在同一张图的右手边我们定义了一个客户端,它使用‘clsState’类并调用其‘PressSwitch()’方法。我们使用‘getStatus’函数在文本框中显示当前状态。

当我们点击‘Press Switch’按钮,灯泡将会从当前状态切换到相反的状态。

Figure: – 状态模式正在进行

你能解释下策略模式吗?

策略模式是一个类内置的算法集,可以根据使用的类交换算法。当你想在运行时决定使用的算法,这个模式会有用。

让我们看一个实际中策略模式如何工作的例子。以数学的计算为例,计算有相加和相减的策略。图片“策略模式正在进行”以形象的方式说明同样的情形。已知两个数,根据策略给出结果。所以如果是相加策略,两个数将会相加,如果是相减策略,将会得到相减的结果。这些策略只不过是算法。策略模式不过是对类内算法的封装而已。

Figure: – 策略模式正在进行

所以我们需要深入的第一件事就是如何封装类内的这些算法。下面的图片“封装算法”显示了‘add’算法如何封装在‘clsAddStatergy’类中,substract’算法如何封装在 ‘clsSubstractStatergy’类中。这两个类都继承自类‘clsStratergy’并重定义了‘calculate’方法。

Figure: – 封装算法

现在我们定义了一个叫做‘clsMaths’的类,它包含一个‘clsStatergy’的引用。这个类包含一个函数‘setStrategy’用于指定策略。

Figure: -策略类和包装类

下面的图片‘策略模式客户端代码’ 显示了如何使用包装类以及如何用‘setStatergy’方法在运行时设置策略

对象。

Figure: – 策略模式客户端代码

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

访问者模式允许我们不用改变实际的类就可以改变类的结构。它是分离当前数据结构和逻辑算法的一种方式。正因为如此,你可以不用改变类的结构就能向当前数据结构添加新逻辑。再一,你可以改变结构而不用触碰逻辑。

参考下面的图片“逻辑和数据结构”,其中有一个顾客(Customer)数据结构。每个顾客(Customer)对象包含多个地址(Address)对象,每个地址(address)对象又包含多个电话(Phones)对象。这个数据结构需要用两种不同的格式输出,一种是简单的字符串格式,另一种是XML格式。所以我们实现了两个类,一个是字符串逻辑类,另一个是XML逻辑类。这两个类遍历对象的结构,给出相应部分的输出。简言之访问者包含这些逻辑。

Figure: – 逻辑和数据结构

让我们根据上面顾客的例子,用C#实现相同的逻辑。如果你使用其它的编程语言,你也能够相应地映射到相同的逻辑。我们已经创建了两个访问者类,一个针对字符串逻辑进行解析,另一个针对XML逻辑。这两个类都有一个‘visit’方法来接收每个对象并进行解析。为了维持一致性,我们通过一个共同的接口‘IVisitor’来实现它们

Figure :- 访问者类

上面定义的访问者类会传给数据结构类,例如,顾客(Customer)类。在顾客(Customer)类,我们在‘accept’方法中传入访问者(visitor)类。在同一个方法中我们传入类类型并且调用其‘visit’方法。‘visit’方法是重载的,这样就可以根据传入的类类型来调用相应的‘visit’方法。

Figure: – 在数据结构类中传入的访问者

现在每个顾客(Customer)有多个地址(Address)对象,每个地址(Address)对象有多个电话(Phones)对象。所以,clsCustomer’类中包含一个objAddresses’列表对象,‘clsAddress’类中包含一个‘objPhones’列表对象。每个对象都有一个‘accept’方法接收访问者类,并把自身传入访问者类的‘visit’方法。因为访问者类的‘visit’方法是重载的,所以它会根据多态性调用正确的访问者方法。

现在我们有了访问者类中的逻辑和顾客(Customer)类中的数据结构,是时候在客户端使用它们了。下面的图片‘Visitor client code’显示了使用访问者模式的示例代码段。我们创建了访问者对象并把它传给顾客数据类。如果想以字符串的格式显示顾客对象的结构,我们就创建‘clsVisitorString’;如果想生成XML格式,就创建‘clsXML’对象并把它传给顾客对象的数据结构。你能够很容易的看出逻辑是如何与数据结构分离的。

Figure: – 访问者客户端代码

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

访问者模式和策略模式看起来非常的相似因为它们都是处理来自数据的封装的复杂逻辑。可以说访问者模式是策略模式更通用的形式。

在策略模式中,我们只有一个上下文或者单个逻辑数据供多个算法操作。在前面的问题中,我们已经解释了策略模式和访问者模式的基础点。那就让我们用先前已经理解的例子进行理解。在策略模式中我们只有唯一一个上下文,并且多个算法在这个上下文中运行。下面的图片‘Strategy’向我们显示了多个算法是如何在这个上下文中运行。

Figure: – 访问者

简而言之,策略模式是一种特殊的访问者模式。在策略模式中我们只有一个数据上下文和多个算法,而在访问者模式中每个算法都关联一个数据上下文。选择策略模式还是访问者模式的基本准则是参考上下文和算法之间的关系。如果上下文和算法是一对多的关系,那么选择策略模式。如果上下和算法是多对多的关系,则选择访问者模式。

简而言之,策略模式是一种特殊的访问者模式。在策略模式中我们只有一个数据上下文和多个算法,而在访问者模式中每个算法都关联一个数据上下文。选择策略模式还是访问者模式的基本准则是参考上下文和算法之间的关系。如果上下文和算法是一对多的关系,那么选择策略模式。如果上下和算法是多对多的关系,则选择访问者模式。

你可以解释下适配器模式吗?

我们常常会碰到两个类因为接口不兼容而不兼容。适配器通过把已有的类重新封装成一个类从而使类之间能彼此兼容。参考下面的图片“不兼容的接口”,这两个类都是用于保存字符串值的集合。并且它们都有一个方法用于把字符串添加到集合。其中一个类的方法命名为‘Add’,另一个的方法命名为‘push’。一个类使用集合对象,而另一个则使用栈。我们想让栈对象可以和集合对象兼容。

Figure: – 不兼容的接口

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

图片‘对象适配器模式’比较宽泛的显示了如何实现这种模式。我们引入一个新的包装类‘clsCollectionAdapter’,它在‘clsStack’类上进行封装,在新的‘Add’方法里调用‘push’方法,从而使两个类兼容。

Figure: – 对象适配器模式

另一种实现适配器模式的方式是通过继承,也称为类适配器模式。图片‘类适配器模式’显示,通过让类‘clsCollectionAdapter’继承类‘clsStack’从而与类‘clsCollection’兼容。

Figure :- 类适配器模式

什么是享元模式(fly weight pattern)?

当我们需要创建许多对象并且这些对象共享一些相同的数据,享元模式非常有用。参考图片“对象和共同数据”。我们需要给一个机构里所有的员工打印名片。数据有两个部分,一部分是可变数据,例如:员工的姓名,另一部分是静态数据i,例如:地址。我们可以只维护一份静态数据的拷贝,让所有可变数据的对象引用这份拷贝,从而减少内存的使用。因此我们为可变数据创建了不同的拷贝,但是却引用了相同的静态数据拷贝。这样我们能优化内存的使用。

Figure: -“对象和共同数据”

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

Figure: – 享元模式的类视图

从图片‘享元模式客户端代码’可以看到,我们创建了两个类‘clsVariableAddress’对象,但是它们内部的静态数据(例如,地址)却引用同一个实例。

Figure: – 享元模式客户端代码

如果你完全不熟悉设计模式或者你其实并不想读整篇文章,你可以看我们免费的视频 design pattern Training and interview questions / answers 



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部