老板回来,我不知道——观察者模式
文章目录
- 老板回来,我不知道——观察者模式
- 双向耦合的代码
- 解耦实践一
- 解耦实践二
- 观察者模式
- 观察者模式的特点
- Java内置接口实现
- 观察者模式的应用
- 石守吉失手机后
老板回来,我不知道——观察者模式
时间:4月12日21点 地点:小菜、大鸟住所的客厅 人物:小菜、大鸟
小菜对大鸟说:“今天白天真的笑死人了,我们一同事在上班期间看股票行情,被老板当场看到,老板很生气,后果很严重呀。”
“最近股市这么火,也应该可以理解的,你们老板说不定也炒股。”
“其实最近项目计划排得紧,是比较忙的。而最近的股市又特别的火,所以很多人都在偷偷地通过网页看行情。老板时常会出门办事,于是大家就可以轻松一些,看看行情,几个人聊聊买卖股票的心得什么的,但是一不小心,老板就会回来,让老板看到工作当中做这些总是不太好,你猜他们想到怎么办?”
“只能小心点,那能怎么办?”
“我们公司前台是一个小美眉,她的名字叫童子喆,因为平时同事们买个饮料或零食什么的,都拿一份孝敬于她,所以关系比较好,现在他们就请小子喆帮忙,如果老板出门后回来,就一定要打个电话进来,大家也好马上各就各位,这样就不会被老板发现问题了。”
“哈,好主意,老板被人卧了底,这下你们那些人就不怕被发现了。”
“是呀,只要老板进门,子喆拨个电话给同事中的一个,所有人就都知道老板回来了。这种做法屡试不爽。”
“那怎么还会有今天被发现的事?”
“今天是这样的,老板出门后,大家开始个个都打开股票行情查看软件,然后还聚在一起讨论着’大盘现在如何’'你的股票抛了没有’等事。这时老板回来后,并没有直接走进去,而是对子喆交待了几句,可能是要她打印些东西,并叫她跟老板去拿材料,这样子喆就根本没有任何时间去打电话了。”
“哈,这下完了。”
“是呀,老板带着子喆走进了办公室的时候,办公室一下子从热闹转向了安静,好几个同事本是聚在一起聊天的,赶快不说话了,回到自己的座位上,最可怜的是那个背对大门的同事——魏关姹,他显然不知道老板回来了,竟然还叫了一句’我的股票涨停了哦。',声音很大,就当他兴奋地转过身想表达一下激动的心情时,却看到了老板愤怒的面孔和其他同事同情的眼神。”
“幸运却又倒霉的人,谁叫他没看到老板来呢。”
“但我们老板很快恢复了笑容,平静地说道:'魏关姹,恭喜发财呀,你是不是考虑请我们大家吃饭哦。'魏关姹面红耳赤地说,‘老板,实在对不起!以后不会了。’'以后工作时还是好好工作吧。大家都继续工作吧。'老板没再说什么,就去忙事情去了。”
“啊,就这样结束了?我还当他会拿魏关姹作典型,好好批评一顿呢。不过回过头来想想看,你们老板其实很厉害,这比直接批评来得更有效,大家都是明白人,给个面子或许都能下得了台,如果真的当面批评,或许魏关姹就干不下去了。”
“是的,生气却不发作,很牛。”
双向耦合的代码
"你说的这件事的情形,是一个典型的观察者模式。"大鸟说,“你不妨把其间发生的事写成程序看看。”
"哦,好的,我想想看。"小菜开始在纸上画起来。
半分钟后,小菜给了大鸟程序。
“写得不错,把整个事情都包括了。现在的问题是,你有没有发现,这个’前台秘书’类和这个’看股票者’类之间怎么样?”
“嗯,你是不是指互相耦合?我写的时候就感觉到了,前台秘书类要增加观察者,观察者类需要前台秘书的状态。”
“对呀,你想想看,如果观察者当中还有人是想看NBA的网上直播(由于时差关系,美国NBA篮球比赛通常都是在北京时间的上午开始),你的’前台秘书’类代码怎么办?”“那就得改动了。”
“你都发现这个问题了,你说该怎么办?想想我们的设计原则?”
“我就知道,你又要提醒我了。**首先开放-封闭原则,修改原有代码就说明设计不够好。其次是依赖倒转原则,我们应该让程序都依赖抽象,而不是相互依赖。**OK,我去改改,应该不难的。”
解耦实践一
半小时后,小菜给出了第二版。
增加了两个具体观察者:
“这里让两个观察者去继承’抽象观察者’,对于’update(更新)'的方法做重写操作。”
“下面是前台秘书类的编写,把所有的与具体观察者耦合的地方都改成了’抽象观察者’。”
客户端代码同前面一样。
“小菜,你这样写只完成一半呀。”“为什么,我不是已经增加了一个’抽象观察者’了吗?”
“你小子,考虑问题为什么就不能全面点呢?你仔细看看,在具体观察者中,有没有与具体的类耦合的?”
“嗯?这里有什么?哦!我明白了,你的意思是’前台秘书’是一个具体的类,也应该抽象出来。”
“对呀,你想想看,你们公司最后一次,你们的老板回来,前台秘书来不及电话了,于是通知大家的任务变成谁来做?”
“是老板,对的,其实老板也好,前台秘书也好,都是具体的通知者,这里观察者也不应该依赖具体的实现,而是一个抽象的通知者。”
“另外,就算是你们的前台秘书,如果某一个同事和她有矛盾,她生气了,于是不再通知这位同事,此时,她是否应该把这个对象从她加入的观察者列表中删除?”
“这个容易,调用’detach’方法将其减去就可以了。”
“好的,再去写写看。”
解耦实践二
又过了半小时后,小菜给出了第三版。
增加了抽象通知者,可以是接口,也可以是抽象类。
具体的通知者类可能是前台秘书,也可能是老板,它们也许有各自的一些方法,但对于通知者来说,它们是一样的,所以它们都去继承这个抽象类Subject。
对于具体的观察者,需更改的地方就是把与"前台秘书"耦合的地方都改成针对抽象通知者。
结果显示:
"由于’魏关姹’没有被通知到,所以他被当场’抓获’,下场很惨。"小菜说道,“现在我做到了两者都不耦合了。”
“写得好。你已经把观察者模式的精华都写出来了,现在我们来看看什么叫观察者模式。”
观察者模式
观察者模式又叫作发布-订阅(Publish/Subscribe)模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。[DP]
观察者模式(Observer)结构图:
Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫作更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个update()方法,这个方法叫作更新方法。
ConcreteSubject类,叫作具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
结果显示:
观察者模式的特点
"用观察者模式的动机是什么呢?"小菜问道。
“问得好,将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便[DP]。而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦Subject的状态发生了改变,所有的Observer都可以得到通知。Subject发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。”
“什么时候考虑使用观察者模式呢?”
"你说什么时候应该使用?"大鸟反问道。
“当一个对象的改变需要同时改变其他对象的时候。”
“**补充一下,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。**还有吗?”
“我感觉当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。”
“非常好,总的来讲,观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。”
"啊,这实在是依赖倒转原则的最佳体现呀。"小菜感慨道。
“我问你,在抽象观察者时,你的代码里用的是抽象类,为什么不用接口?”
“因为我觉得两个具体观察者,看股票观察者和看NBA观察者是相似的,所以用了抽象类,这样可以共用一些代码,用接口只是方法上的实现,没太大意义。”
“那么抽象观察者可不可以用接口来定义?”
“用接口?我不知道,应该没必要吧。”
“哈,那是因为你不知道观察者模式的应用都是怎么样的。现实编程中,具体的观察者完全有可能是风马牛不相及的类,但它们都需要根据通知者的通知来做出update()的操作,所以让它们都实现下面这样的一个接口就可以实现这个想法了。”
"嘿,大鸟说得好,这时用接口比较好。"小菜傻笑问道,“那具体怎么使用呢?”
Java内置接口实现
"事实上,Java已经为观察者模式准备好了相关的接口和抽象类了。“大鸟 说 道 , " 观 察 者 接 口 java.util.Observer 和 通 知 者 类 java.util.Observable 。 有 了 这 些 Java 内 置 代 码 的 支 持 , 你 只 需 要 扩 展 或 继 承Observable,并告诉它什么时候应该通知观察者,就OK了,剩下的事Java会帮你做。JDK中的原生代码你可以自己去查看,我们来看如何用它们来实现刚才的代码结构。”
" 由 于 已 经 有 了 Observable 实 现 的 各 种 方 法 , 比 如 加 观 察 者( addObserver ) 、 减 观 察 者 ( deleteObserver ) 、 通 知 观 察 者(notifyObservers)等。所以Boss类继承了Observable,已经无须再实现这些代码了。Boss继承Observable类,当addObserver添加一些观察者后,它在setAction里是这样工作的:调用setChanged方法,标记状态已经改变,然后调用notifyObservers方法来通知观察者。"
“StockObserver实现了JDK中的Observer接口,但update有两个固定参数 , 其 中 Observable 对 象 可 以 让 观 察 者 知 道 是 哪 个 主 题 通 知 它 的 NBAObserver与此类类似,就不写了。看代码。”
“与小菜你原来的写法差不多,addObserver与attach,deleteObserver与detach都是同一个意思。”
小菜:“这样看来,代码省下了很多了。”
大鸟:“你有发现问题吗?比如如果增加前台秘书类,会不会有问题。”
小菜:“在StockObserver类中,竟然出现了Boss,具体类中耦合了具体类了,这就没有针对接口编程了。”
大鸟:“上面这样的设计,可以充分复用Java内置类和接口,达到针对接口编程的目的,又保证了我们的代码不会因为紧耦合而不能复用的问题。”
大鸟:“事实上,这里Java内置的Observable是一个类,这样设计是有问题的。一个类,就只能继承它,但我们自己的类可能本身就需要继承其他抽象类,这就产生了麻烦。Java不支持多重继承,这就严重限制了Observable的复用潜力。所以,当你这段代码用javac编译时,会给出提示:警告:[deprecation] java.util中的Observable已过时。系统其实是建议你不要复用这样的方法。所以真实编程中,我们也要考虑怎么取舍,如何修改的问题。”
观察者模式的应用
小菜问道:“那在现实中,观察者模式主要用在哪里呢?”
大鸟微笑着说道:“举个例子。我们所用的几乎所有的应用软件,内部窗体相互通信就都是利用观察者模式的原理在工作的。比如,使用Word时,当你单击右上角的’样式窗格’之前的时候,整个界面是下面这样的。”
“当’样式窗格’之后,右侧会弹出一个样式编辑的窗体。也就是说,一个开关按钮给一个样式编辑窗体的观察者发了通知,让它显示出来。”
“所有的控件,事实上都是有实现’Observer’的接口(实际编程比较复杂,这里不展开),它们都是在等通知中,单击某个开关按钮后,就向这些控件——观察者发了通知,于是产生了窗体上的变化。这其实就是观察者模式的实际应用。具体细节可以去了解Android或Swing编程。”
石守吉失手机后
突然小菜的手机响了。
“小菜,我是石守吉,昨天我手机丢了,没办法,只得重买一个。原来的那个号也没法办回来,还好我记得你的手机,所以用这个新号打给你了。你能不能把我们班级同学的号码抄一份发邮件给我?”
“哦,这个好办,不过班级这么多人,我要是抄起来,也容易错。而且,如果现在同学有急事要找你,不就找不到了吗?我们这样办吧……用观察者模式。”
“你说什么?我听不懂呀。什么观察者模式?”
“哈,其实就是我在这里给我们班级所有同学群发一条短消息,通知他们,你石守吉已换新号,请大家更新号码,有事可及时与石守吉联系。”
“好办法,你可记得一定要给李MM、张MM、王MM发哦。”
“你小子,首先想着的就是MM。放心吧,我才不管谁呢,凡是在我手机里存的班级同学,我都会循环遍历一遍,群发给他们的。”
“小菜怎么张口闭口都是术语呀,好的,你就循环遍历一下吧。这事就委托给你了,谢谢哦!”
如果对你有帮助,就一键三连呗(关注+点赞+收藏),我会持续更新更多干货~~