大话设计模式十二之观察者模式

最近项目计划排得紧,股市特别火,所以很多人都在偷偷地通过网页看行情。老板市场会出门办事,如果老板回来,让前台秘书打一个电话进来,大家也好马上各就各位,这样就不会被老板发现问题了。

一、双向耦合的代码
其间发生的事情的代码



把整个事情都包括了,现在的问题是,这个‘前台’类和这个‘看股票者’类之间互相耦合。前台类要增加观察者,观察者类要前台的状态。
如果观察者当中还有人是想看NBA的网上直播,‘前台’类代码需要修改。
首先开放-封闭原则,修改原有代码就说明设计不够好,其次是依赖倒转原则,我们应该让程序都依赖抽象,而不是互相依赖。

二、解耦实践一
这里让两个观察者去继承‘抽象观察者’,对于‘Update(更新)’的方法做重写操作


前台秘书类的编写,把所有的与具体观察者耦合的地方都改成了‘抽象观察者’

客户端代码同前面一样
在具体观察者中,有与具体的类耦合的。‘前台秘书’是一个具体的类,也应该抽象出来。老板回来,前台来不及电话了,于是通知大家的任何变成了老板来做。
其实老板也好,前台也好,都是具体的通知者,这里观察者也不应该依赖具体的实现,而是一个抽象的通知者。
如果某一个同事和前台有矛盾,于是不再通知这位同事,此时,她应该把这个对象从她加入的观察者列表中删除。

三、解耦实践二
增加了抽象通知接口
具体的通知者可能是前台,也可能是老板,它们也许有各自的一些方法,但对于通知者来说,它们是一样的,所以它们都去实现这个接口。

前台秘书类与老板类类似。对于具体的观察者,需要更改的地方就是把‘前台’耦合的地方都改成针对抽象通知者。

由于‘魏关姹’被取消了注册,没有被通知到,所以下场很惨。现在做到了两者都不耦合了。


结构图如下:


四、观察者模式
观察者模式又叫做发布-订阅(Publish/Subscribe)模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。


Subjec类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。凑下观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update()方法,这个方法叫做更新方法。

ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。


观察者模式的特点:
将一个系统分割成一系列相互协作的类有一个很不好的副作用那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。

当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将者两者封装在独立的对象中使它们各自独立地改变和复用。
观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。
这其实是依赖倒转原则的最佳体现。
在抽象观察者时,你的代码里用的是抽象类,为什么不用接口?
因为两个具体观察者,看股票观察者和看NBA观察者类是相似的,所以用了抽象类,这样可以共用一些代码,用接口只是方法上的实现,没什么太大意义。
那么抽象观察者可不可以用接口来定义?
现实编程中,具体的观察者完全有可能是风马牛不相及的类,但它们都需要根据通知者的通知来做出Update()操作,所以让它们都实现下面这样的一个接口就可以实现这个想法了。
这些控件要么是.NET类库,要么是别人事先写好的控件,它如何再能去实现拥有Update的Observer接口呢?


观察者模式的不足:
当打开VS2015,当运行程序以后,除了弹出一个控制台的程序窗体以外,工具栏发生了变化,工具箱不见了,‘错误列表’编程了‘自动窗口’和‘命令窗口’打开。仅仅是点击了一个“运行”按钮,就发生了这么多的变化,而各个变化都涉及到不同的控件。
没有办法让每个控件都去实现一个‘Observer’的接口,因为这些控件都早已被它们的制造商给封装了。
尽管当点击“运行”按钮时,确实是在通知相关的控件产生变化,但它们是不可能用接口的方式来实现观察者模式的。
还是和刚才说的问题一样呀,尽管已经用了依赖倒转原则,但是‘抽象通知者’还是依赖‘抽象观察者’,也就是说,万一没有了抽象观察者这样的接口,我这通知的功能就完不成了。
另外就是每个具体观察者,它不一定是‘更新’的方法要调用呀,就像刚才说的,我希望的是‘工具箱’是隐藏,‘自动窗口’是打开,这根本就不是同名的方法。这应该就是它不足的地方吧。
如果通知者和观察者之间根本就互相不知道,由客户端来决定通知谁,那就好了。



事件委托实现:
“看股票观察者”类和“看NBA观察者”类,去掉了父类“抽象观察类”,所以补上一些代码,并将“更新”方法名改为各自适合的方法名。

现实中就是这样的,方法名本就不一定相同。
“抽象通知者”由于不希望依赖“抽象观察者”,所以“增加”和“减少”的方法也就没有必要了(抽象观察者已经不存在了)

下面就是如何处理‘老板’类和‘前台’类的问题。声明一个委托,名叫“EventHandler(事件处理程序)”,无参数,无返回值。


委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。
委托可以看作是对函数的抽象,是函数的‘类’,委托的实例将代表一个具体的函数。
即‘delegate void EventHandler();’可以理解为声明了一个特殊的类,而‘public event EventHandler Update;’可以理解为声明了一个事件委托变量叫‘更新’。
委托的实例将代表一个具体的函数,意思是说,‘new EventHandler(tongshi1.CloseStock Market)’其实就是一个委托的实例,而它就等于将‘tongshi1.CloseStockMarket’这个方法委托给‘huhansan.Update’这个方法了
一旦委托分配了方法,委托将与该方法具有完全相同的行为。
一个委托可以搭载多个方法,所有方法被依次唤起。
可以使得委托对象所搭载的方法并不需要属于同一个类。
这样就使得,本来是在‘老板’类中的增加和减少的抽象观察者集合以及通知时遍历的抽象观察者都不必要了。转到客户端让委托搭载多个方法,这就解决了本来与抽象观察者耦合的问题。

但委托也是由前提的,那就是委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。
注意,是先由观察者模式,再有委托事件技术的,它们各有优缺点。


猜你喜欢

转载自blog.csdn.net/nicolelili1/article/details/80076819
今日推荐