Head first设计模式(2)

认识观察者模式

我们看看报纸和杂志的订阅是怎么回事:

1、报社的业务就是出版报纸

2、向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸

3、当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来

4、只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸

 

出版者+订阅者=观察者模式

如果你了解报纸的订阅是怎么回事,其实就知道观察者模式是怎么回事,只是名称不太一样:出版者改称为“主题”(Subject),订阅者改称为"观察者"(Observer)

1、主题对象管理某些数据

2、当主题内的数据改变就会通知观察者

3、一旦数据改变,新的数据会以某种形式送到观察者手上

4、观察者订阅(注册)主题以便在主题数据改变时能够收到更新

5、某个对象不是观察者,所以在主题数据改变使不会被通知

 

观察者的一天

1、鸭子对象过来告诉主题,它相当一个观察者

2、鸭子其实想说的是:我对你的数据改变感兴趣,一有变化请通知我

3、鸭子对象现在已经是正式的观察者了

4、鸭子静候通知,等待参与这项伟大的事情。一旦接获通知,就会得到一个整数

5、主题有了新的数据值

6、现在鸭子和其他所有观察者都会收到通知:主题已经改变了

7、老鼠对象要求从观察者中把自己除名

8、老鼠已经观察此主题太久,厌倦了,所以决定不再当个观察者

9、老鼠离开了

10、主题知道老鼠的请求之后,把它从观察者中除名

11、主题有一个新的整数

12、除了老鼠之外,每个观察者都会收到通知,因为它已经被除名了。嘘!不要告诉别人,老鼠其实心中暗暗地怀念这个整数,或许哪天又会再次注册,回来继续当观察者

 

定义观察者模式

1、当你试图勾勒观察者模式时,可以利用报纸订阅服务,以及出版者和订阅者比拟这一切

2、在真实的世界中,你通常会看到观察者模式被定义成

3、观察者模式——定义了对象之间的一对多依赖,这样依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新

4、主题和观察者定义了一对多的关系,观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新

5、实现观察者模式的方法不只一种,但是以包含SubjectObserver接口的类设计的做法最常见

 

定义观察者模式:类图

1、这是主题接口,对象使用此接口注册为观察者,或者把自己从观察者中删除

2、每个主题可以有许多观察者

3、所有潜在的观察者必须实现观察者接口,这个接口只有update()一个方法,当主题状态改变时它被调用

4、一个具体主题总是实现主题接口,除了注册和撤销方法之外,具体主题还实现了notifyObservers()方法,此方法用于在状态改变时更新所有当前观察者

5、具体主题也可能有设置和获取状态的方法

6、具体的观察者可以是实现此接口的任意类。观察者必须注册具体主题,以便接收更新

 

这和一对多的关系有何关联?

利用观察者模式,主题是具有状态的对象,并且可以控制这些状态。也就是说,有“一个”具有状态的主题。另一方面,观察者使用这些状态,虽然这些状态并不属于它们。、有许多的观察者,依赖主题告诉它们状态何时改变了。这就产生一个关系:"一个"主题对“多个”观察者的关系

 

其间的依赖是如何产生的?

因为主题是真正拥有数据的人,观察者是主题的依赖者,在数据变化时更新,这样比起让许多对象控制同一份数据来,可以得到更干净的OO设计

 

松耦合的威力

1、当两个对象之间松耦合,它们依赖可以交互,但是不太清楚彼此的细节

2、观察者模式提供了一种对象设计,让主题和观察者之间松耦合

为什么?

1、关于观察者的一切,主题只知道观察者实现了某一个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁,做了些什么或其他任何细节

2、任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会收到任何影响。同样的,也可以在任何时候删除某些观察者

3、有新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象

4、我们可以独立地复用主题或观察者。如果我们在其他地方需要使用主题和观察者,可以轻易地复用,因为二者并非紧耦合

5、改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵循,我们就可以自由地改变他们

6、设计原则——为了交互对象之间得松耦合设计而努力

7、松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低

 

把观测值直接传入观察者中是更新状态的最直接的方法,你认为这样的做法明智吗?

这些观测值的种类和个数在未来有可能改变吗?如果以后会改变,这些变化是否被很好地封装?或者是需要修改许多代码才能办到?

关于将更新的状态传送给观察者,你能否想到更好的方法解决此问题?

 

围炉夜话:主题与观察者

主题:我很高兴,我们终于有机会面对面聊天了

观察者:是这样吗?我以为你根本不在乎我们这群观察者呢?

主题:哎呀!我把该做的事都做到了,不是吗?我总是会通知你们发生什么事了....我虽然不知道你们是谁,但这不意味着我不在乎你们。况且,我知道关于你们的一件重要的事:你们实现了Observer接口

观察者:是呀,但只是关于我的一小部分罢了!无论如何,我对你更了解

主题:是吗?说来听听!

观察者:嗯!你总是将你的状态传给我们,所以我们可以知道你内部的情况。有时候,这很烦人的.....

主题:拜托,我必须主动送出我的状态和通知给大家,好让你们这些懒惰的观察者知道发生什么事了

观察者:咳!等等,我说主题先生,首先,我们并不懒,在你那些"很重要"、通知的空档中,我们还有别的事要做。另外,为何由你主动送数据过来,而不是让我们主动去向你索取数据?

主题:嗯——这样或许也行,只是我必须因此门户大开,让你们全部都可以进来取得你们需要的状态,这样太危险了。我不能让你们进来里面大肆挖掘我的各种数据

观察者:你何不提供一些公开的getter方法,让我们“拉”走我们需要的状态?

主题:是的,我可以让你们“拉”走我的状态,但是你不觉得这样你们反而不方便吗?如果每次想要数据时都来找我,你可能要调用很多次才能收集齐全你所要的状态。这就是为什么我更喜欢"推的原因,你们可以在一次通知中一口气得到所有东西

观察者:死鸭子嘴硬!观察者种类这么多,你不可能事先料到我们每个人的需求,还是让我们直接去取得我们需要的状态比较恰当,这样一来,如果我们有人只需要一点点数据,就不会被强迫收到一堆数据。这么做同时也可以在以后比较容易修改。比方说,哪一天你决定扩展功能,新增更多的状态,如果采用我建议的方式,你就不用修改和更新对每位观察者的调用,只需改变自己来允许更多的getter方法来取得新增的状态

主题:是的,两种做法都有各自的优点。我注意到Java内置的Observer模式两种做法都支持

观察者:真的吗?

主题:太好了,或许我会看到一个""的好例子,因而改变我的想法

 

使用Java内置的观察者模式

到目前为止,我们已经从无到有地完成了观察者模式,但是,Java API有内置得观察者模式。java.util包(package)内包含最基本的Observer接口与Observable类,这和我们的Subject接口与Observer接口很相似Observer接口与Observalbe类使用上更方便,因为许多功能都已经事先准备好了。你甚至可以使用推(push)或拉(pull)的方式传送数据,稍后就会看到这样的例子。

有了Java内置的支持,你只需要扩展(继承)Observable,并告诉它何时该通知观察者,一切就完成了,剩下的事API会帮你做

 

setChanged()方法

setChanged()方法用来标记状态已经改变的事实,好让notifyObservers()知道当它被调用时应该更新观察者。如果调用notifyObservers()之前没有先调用setChanged(),观察者就“不会”被通知

1setChanged()方法把changed标志设为true

2notifyObservers()只会在changed标为"true"时通知观察者

3、在通知观察者之后,把changed标志社回false

这样做有其必要性,setChanged()方法可以让你在更新观察者时,有更多的弹性,你可以更适当地通知观察者。比方说,如果没有setChanged()方法,我们得气象站测试是如此敏锐,以至于温度计读书每十分之一度就会更新,这会造成WeatherData对象持续不断地通知观察者,我们并不希望看到这样的事情发生。如果我们希望半度以上才更新,就可以在温度差距到达半度时,调用setChanged(),进行有效的更新。

你也许不会经常用到此功能,但是把这样的功能准备好,当需要时马上就可以使用。总之,你需要调用setChanged(),以便通知开始运转。如果此功能在某些地方对你有帮助,你可能也需要clearChanged()方法,将changed状态设置回false。另外也有一个hasChanged()方法,告诉你changed标志的当前状态

 

不要依赖于观察者被通知的次序

1java.util.Observable实现了它的notifyObservers()方法,这导致了通知观察者的次序不同于我们先前的次序。谁也没有错,只是双方选择不同的方式实现罢了

2、但是可以确定的是,如果我们的代码依赖这样的次序,就是错的。为什么呢?因为一旦观察者/可观察者的实现有所改变,通知次序就会改变,很可能就会产生错误的结果,这绝对不是我们所认为的松耦合

 

难道Java.util.Observable违反了我们的OO设计原则:针对接口编程,而非针对实现编程?

java.util.Observable的黑暗面

1、是的,可观察者是一个“类”而不是一个“接口”,更糟糕的是,它甚至没有实现一个接口。不幸的是,java.util.Observable的实现有许多问题,限制了它的使用和复用。这并不是说它没有提供有用的功能,我们只是想提醒大家注意一些事实

2Observable是一个类,你已经从我们的原则得知这不是一件好事,但是,这到底会造成什么问题呢?

3、首先,因为Observable是一个“类”,你必须设计一个类继承它。如果某类想同时具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承,这限制了Observable的复用潜力(而增加复用潜力不正是我们使用模式最原始的动机吗?)

4、再者,因为Observable接口,所以你无法建立自己的实现,和Java内置的ObserverAPI搭配使用,也无法将java.util的实现换成另一套做法的实现(比方说,Observable将关键的方法保护起来,如果你看看ObservableAPI,你会发现setChanged()方法被保护起来了(被定义成protected),那又怎么样呢?这意味着:除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来,这个设计违反了第二设计原则:"多用组合,少用继承"

5、如果你能够扩展java.util.Observable,那么Observable"可能"可以符合你的需求,否则,你可能需要像之前的方式自己实现这一整套观察者模式,不管用哪种方法,反正你都已经熟悉观察者模式了

 

JDK中,还有哪些地方可以找到观察者模式

JDK中,并非只有在java.util中次啊能找到观察者模式,其实在JavaBeansSwing中,也都实现了观察者模式。

 

OO原则

1、封装变化

2、多用组合,少用继承

3、针对接口编程,不针对实现编程

4、为交互对象之间的松耦合设计而努力

 

OO模式

观察者模式(Observer pattern)——在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新

 

要点

1、观察者模式定义了对象之间一对多的关系

2、主题(也就是可观察者)用一个共同的接口来更新观察者

3、观察者和可观察者之间用松耦合方式结合(loosecoupling),可观察值不知道观察者的细节,只知道观察者实现了观察者接口

4、使用此模式时,你可从被观察者处推(push)或拉(pull)数据(然而,推的方式被认为更“正确”)

5、有多个观察者时,不可以依赖特定的通知次序

6、Java有多种观察者模式的实现,包括了通用的java.util.Observable

7、要注意java.util.Observable实现上所带来的一些问题

8、如果有必要的话,可以实现自己的Observable,这并不难,不要害怕

9、Swing大量使用观察者模式,许多GUI也是如此

10、此模式也被应用在许多地方,例如:JavaBeans、RMI


观察者的代表人物——MVC

 

观察者模式包含的OO设计原则

1、在观察者模式中,会改变的是主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题,这就叫提前规划

2、主题与观察者都使用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者,这样可以让两者之间运作正常,又同时具有松耦合的优点

3、观察者模式利用“组合”将许多观察者组合进主题中,对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式而产生的


转载于:https://my.oschina.net/u/814431/blog/361032

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值