您的位置:首页 > 编程语言

观察全世界(观察者模式的可复用代码实现)(上)

2008-03-29 13:35 274 查看
设计模式是我们在工作中常常要使用的,但是现实往往是残酷的,我们在复用设计模式的同时往往做不到去复用其实现的代码,常常就是复用了设计模式的设计部分,但是复用不了其代码部分,这样设计模式作为可复用的基础就没有将其威力完全发挥出来,这其中的很重要的原因就是我们往往在复用设计模式的时候都是参考GOF的经典描述来实现的,常常拘泥于模式的类结构图来实现,而不能跳出去,其实GOF描述的模式我们需要的是深谙其精髓即:具体设计模式的思想是什么!而不是其具体的实现例子,尤其是那个类图,那些只不过是用来表达思想罢了,真正吸收了思想之后就要跳出书上类图的框框条条了。前些天看到一篇文章不错,做了这方面的尝试,我就想翻译出来与大家共同思考,翻译水平有限,希望大家批评指正:)整个文章我分成上下两部分贴出来。

原文出处:http://www.codeproject.com/KB/architecture/reuse_observer.aspx

观察这个世界—

怎样构建一个设计模式的可复用实现

摘要:
这篇文章利用观察者模式的例子讲述了怎样能够将一个可复用设计的思想变成一个实实在在的可复用的代码。在一个关于一般模式简短的复述后,对于观察者模式,我将给你展示由于依赖继承而造成的将其应用于特定问题的典型缺陷。我还将给出一些通用技术来削减这种依赖进而最终展示出一个能够很容易应用于你的项目中的可复用的观察者模式。你应当熟悉一般模式的思想、观察者模式、面向对象分析/面向对象设计的概念,以及C++模板机制。
介绍:
可复用的设计……
“一个设计模式命名、提炼并且辨识出一个常用设计结构关键的方方面面,来使得其有益于创建一个可复用的面向对象的设计”【Gamma】
正如Gamma所讲的那样,一个设计模式是基于创建面向对象软件设计的时候的共性问题。它在帮助你与其他开发者在关于你的高度抽象的设计的交流上确实很棒。但是,有那么一天你将不得不将设计转化为具体实现的时候,也就是意味着你不得不将设计模式融入到一些代码中去。
……相对于可复用代码
在这一点上你将发现你自己一次又一次地做了相同的编码。如果有一些可复用的代码来用于重用的设计不是更好些吗?这篇文章的剩余部分将用一个观察者模式的例子向你展示如何能够达到这一点。
观察者模式
观察者模式是在实践中最好的并且最可用的模式之一。这来自于它的简单以及它基于一个非常共性问题的事实,那就是:将一个对象状态的改变传递给与其相关的对象的问题。
有一篇出自于T.Kulathu Sarm的关于观察者模式的极其优秀的文章,这里在Codeproject[2]的引用处,因此我不打算攒述。下图(修改了一点点见【1】说明)展示了观察者模式的结构:



问题:
我们的目标是创建一个可复用的观察者模式的实现。那看起来简单,就只是将上面的结构直接放入一个类Subject和一个抽象接口IObserver然后当你在需要实现观察者模式的时候使用他们就可以了。但是在真实的项目中你将会发现很难重用你的代码。这个主要的问题在于它在项目架构中引入了结构性的依赖,而这些依赖就来自于继承的使用。
为什么继承是不好的
你们有人可能会说:“拜托,继承那里有那么糟?是继承帮助我们构建了可复用的成分!”当然你是对的。继承的在面向对象编程的概念上如果不是最强大的也只少是非常强大的。但是一般来讲继承的可用性基本上就止步于Kind-of关系了。对于观察者模式来说将一个现实世界中的具体事物,比如在天气预报中使用的一个温度传感器,叫做“一种对象”,听起来就十分奇怪了。而相反更容易将状态的改变通知其他对象的可能性想成我们的温度传感器的一种特性。
继承的结构性影响:
1. 继承树的修改:
看如下的一个带有继承自某些基类的类TempSens(一个温度传感器)和类HurrDet(一个飓风警报探测器)的一个自动天气预报系统的类结构。我们不想对这些基类做任何假设,因此我们就叫他们A和B。



现在我们将类HurrDet当作类TempSens的一个观察者。为了复用我们的实现,类TempSenc不得不继承自类Subject并且类HurrDet继承自IObserver接口。这的确需要技巧,因为我们必须:
l 改变现有继承树或者
l 使用多重继承



方案 a)引入了继承树的改变往往是不能接受的。方案 b)使用了多重继承为类TempSens添加必要的功能。类Subject被当作一个混合类使用,这个混合类将功能混入我们的具体类中去。这是个非常常见的方式。但是在多个类上的多重继承需要慎重考虑同时大多数人都试图避免它。最终大多数的开发者都将不能重用我们的类Subject来为这个特别的情况构建他们自己的观察者模式的实现。换句话说:他们可以复用设计-但是不能复用代码:



2. 服务、观察多种对象:
现在我们添加一个新类Strategy,这个类描述了使用于所有天气预报部分的算法。这些算法被一些气象学家一次又一次地使用来在这个系统中获得新的研究结果。我们想将类HurrDet作为TempSens类与Strategy类的对象们的一个观察者。这意味着HurrDet类现在作为两个独立的观察者了。
与此同时我们向我们的TempSens类里添加一个新的特性。由于温度传感器的可靠性是这个系统的关键,因此它应当能够监控自己的物理状态并且将其报告给技术人员。你可能会猜想,这个是另外一个观察者模式的绝佳应用。我们的TempSens类现在是一个拥有了两个非常不同的行为的事物。一个是用作传播温度信息,另外一个用作传播物理状态信息。
在这点上,使用继承手段的复用是失败的。我们不能将我们的TempSens类 不止一次地派生自Subject或者将HurrDet不止一次地派生自IObserver。确实,我们不得不创建许多的作为接口的IObserver,同时在这里我们又一次只是复用设计,而不是复用代码:



结论:
上面的例子说明试图在现实的项目中复用通用部分的代码,如:我们观察者模式的实现代码,是多么地困难。这是由于我们对于项目的结构做了太多的假定:
1. 它必须能够使用继承来添加功能。
2. 每个类都只需要这个功能至多一次。

解决方案:
注意!(上面的)两个假设都是使用继承的缺点,这一点很重要!明白了这一点,我们就能够很清楚我们必须找到一个不依赖于继承的方法来构建我们的代码。我们能够用聚合代替继承来做到这一点。不是继承自拥有某些函数的基类,而是我们包含它作为一个数据成员(聚合它)。但是,数据成员通常被声明成私有的,同时不能被其他的类所访问。因此,我们需要为外部的类添加成员函数来导出聚合的接口。这些成员函数说起来很俏皮,其实他们只不过是调用了聚集中的相应成员,被当作委托罢了。委托是COM开发中非常流行的技术,因为COM缺少继承的实现,因此委托是其复用实现的唯一方式。
不用继承来实现Subject类:
我们从实现Subject类开始,因为这个是最容易搞定的部分了。不用将我们具体的事物类TempSens继承自Subject,而是我们聚合它,使用委托的方法。这就解决了我们所有的问题了,因为利用聚集很容易在TempSens类中包含两次。如上所述,我们必须从TempSens中(部分)导出Subjects的接口,因为其他的对象必须依然能够作为观察者对他们自己做attach(附着操作)或者Detach(分离操作):



在我们的TempSens类里用聚集替换继承十分简单。这是因为我们不再需要Subject类的抽象接口了。确实观察者在语义上连接了具体事物类Tempsens且需要知道它的接口。因此,Attach()与Detach()方法作为TempSens接口的一部分而不是继承自某些抽象接口类ISubject,就没有什么问题了。

不用继承来实现观察者:
但是,在观察者这一边,情况就非常复杂了。在运行时,事物可能被任意数量的观察者连接,这些观察者可能是不同类的实例。在状态改变的时候,事物类(Subject)就会为所有的观察者对象调用Update()方法,因此所有的观察者都要继承自抽象接口类IObserver。换句话来说:这里我们只需要Update()作为抽象接口的一部分,同时我们不可能将其移动到具体的观察者类当中去。
为了搞定这个我们再次使用委托,但是现在是另外一种方向。我们创建一个辅助类ObserverAdapter来实现IObserver的接口。我们将这个类聚集到我们的HurrDet类中。ObserverAdapter::Update()的实现只是调用外部的HurrDet类的成员函数而已—HurrDet类将这个调用委派给外部类了。



因为我们不想为每一个观察者都写这么一个特别的辅助类,因此我们泛化我们ObserverAdapter的实现:我们用外部类参数化它,同时使用外部类的成员回调来做委托。把成员函数用作回调函数没有那么简单。可能这是由于在C++中指向成员构造函数指针的令人迷惑的语法所造成的吧。但是如果你看一看底下类ObserverAdapter的代码,你会发现把成员函数作为回调函数使用也不是真的那么困难。(To be continued......)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: