读书笔记-设计模式-可复用版-Observer 观察者模式
wikipedia:
https://en.wikipedia.org/wiki/Observer_pattern
对于设计模式,重要的是要知道他的意图,能够解决什么样的问题,优缺点有哪些,这就够了,而不必去深究书中的每一个细节,GOF书中提到了很多在不同情境下要面对的情况以及解决方案,他更像是一本工具书,我们在应用特定设计模式的时候,看看它符合书中提到的哪些点,然后参考建议去实现, 这样会更有助于我们理解它。
意图:
定义对象间的一种,一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象,都会得到通知并自动更新。
生活中最为常见的微信公众号订阅,YOUTUBE频道订阅(a millions of subscribers)微博上的关注,当有内容更新时,订阅者(subscribes),即观察者(observers),可以得到通知。
可以说这是任何一个商业项目中,都会应用到的设计模式了~
Observer 观察者模式 又称发布-订阅模式(Publish-Subscribe)
解释:
我通常将一个系统分割成一系列相互协作的类,但这种方式通常会有一个常见的副作用,这些相互协作的类之间通常交互是比较紧紧密的,这就会便他们之间的耦合性变高,降低了可重用性。
书上的一个例子:
image.png目标数据{a=50%,b=30%,c=20%}
会由三种 图表Graph的方式来表示,当数据变更的时候,三种图表均需要进行更新,显示最新的数据,同样,如果图表是可编辑的状态,那么我修改了图标里的内容,目标数据要进行更新,其它的两个图标也要跟着更新,可以想象一下,我们将上图想象成为四个对象,每一个对象中,都要有其它三个对象的引用,这样我们才能够通过引用去更新其它对象,这种耦合性非常重,重用性很差。
观察者模式Observer,可以解决这种实际场景的,降低耦合性,提高重用性,对象之间,不需要建立直接的引用关系。
Observer描述了如何建立这种关系,关键对象是目标(subject)和观察者(Observer)
一个Subject目标,可有任意数目依赖于它的观察者Observer。
一旦目标的状态发生变化,所有的观察者都将得到通知并做出响应。
这种模式也叫publish-observer 发布订阅,publish发布就是上面的目标subject
当我们提到观察者模式的时候,首先要想到的两个重要对象:
目标Subject
观察者Observer
Subject可以有任意数目依赖于它的Observer,当Subject发生改变的时候,所有的Observer都会得到通知,并做出响应。
结构图:
image.pngSubject->基本类或是接口,包括了通用的Attach,Detach,Notify
Observer->是观察者的基类或是接口,包括了Update,具体对消息通知进行响应处理 。
ConcreteSubject->具体的Subject,继承自Subject,当发生改变时,会通知所有注册它的观察者。
concreteObserver->具体的观察者,注册到具体的Subject上,并接受Subject的改变通知,做出响应更新。
目标和观察者之间抽象耦合:
目标和观察者之间是抽象的耦合,即目标只知道他有一系列的观察者,这些观察者有着共同的基类或是接口,但目标并不知道这些观察者各自属于哪一个具体的类,这样,他们之间的耦合性是最小的。所谓的面向接口编程。
广播通知:
当目标发生改变的时候,会通知所有注册它的观察者,并不会指定某一个特定的观察者,目标并不关心有多少观察者对此感兴趣,目标在变化的时候,就会通知所有的观察者,至于观察者如何处理,这不是目标所关心的。
缺点:
1.意外的更新:一个观察进并不知道其它观察者的存在,那么当更新了其中一个观察者的时候,其它的观察者也会进行更新,但有时候这种操作并不是必要的,比如一个目标,可能有多达几十个观察者,所以在使用中,需要注意这种情况,比如可以进行更为细致的拆分,比如加入类型,只更新注册了对应类型的观察者即可,避免了所有观察者都进行更新。
当然,这里还会涉及到另外一个问题,就是无效监听Lapsed Listener,这会在后面提到。
2.Attach&Detach(现已废弃):
这是要成对出现的操作,如果我们进行了Attach操作,就必须要有对应的Detach,就像C/C++中那样,分配了内存,就必须进行释放,否则就会出现memory leak。
(When you have registered a content observer, it is your responsibility to also unregister it. Otherwise you would create a memory leak and your Activity would never be garbage collected.
我们注册一个observer,就有义务去unregister反注册它,否则当前的对象就没有办法被垃圾回收器处理,产生memory leak!)
大话设计模式里关于Observer的解释是非棒的,从面向具体的双向耦合的实现到面向抽象解耦,再到通过C# Event实现方式,下面是自己从书中总结的一些笔记:
双向耦合的代码:
image.png image.png image.pngSecretary->Subject
StockObserver->Observer
Observer模式的思想已经实现了,但Secretary and StockObserver之间是相互耦合,强耦合,如果此时我再添加别的类型的Observer,那么Secretary类就必须做出修改,这种糟糕的设计,非常的不灵活,不健壮,并且也非违反了依赖倒置原则,应该在抽象的层面,相互依赖,而不是具体的类。
应用面向具体,设计要面向抽象!
那么从实现的角度,Secretary和StockObserver都应该是派生类,他们分别有自己的抽象基类Subject和Observer.
更好的实现版本:
image.png image.png image.png在抽象层面,相互依赖,这样,如果我再添加其它的observer,只需继承observer基类实现即可,并不会影响到subject.
应用面向具体,设计要面向抽象。
但这种版本也是存在问题的,观察者一定得是Observer的子类吗,观察者可以是任何类,风马牛不相干的类,而且他们也不可能都被定义为 Update方法,容易产生歧义,
所以现在的Observer的实现方案基本上都是基于委托实现的。
基于委托的好处,我们不需要一个List来维护Observer,我们不能强制要求观察者都要继承自同一个父类,这在开发中不现实,更不可取,委托就可以很好的解决这个问题,委托是引用类型,是一个方法列表,这个列表中的方法只需要拥有相同的参数列表和返回值类型,就可以满足条件。
代码如下:
image.png image.png image.png新增的Neymar类,和observer1,observer2是完全不相关的类。
观察者不再被要求必须拥有同一个父类,可以是风马牛不相干的任何类。
但在使用委托的过程中 ,就需要注意我们下面会提到的Lapsed Listener,不需要接受通知的方法要及时Remove掉。
3.Lapsed Listener:#
http://ilkinulas.github.io/development/general/2016/04/17/observer-pattern.html
摘取其中的一段:
So what can possibly go wrong?
In a runtime where memory is managed automatically like Java or C#, the subject holds a strong reference to the observers, keeping them alive. The strong reference from the subject (the observed object) to the observer, prevents the observer (and any objects it references) from being garbage collected until the observer is unregistered. A memory leak happens, if the observer fails to unregister from the subject when it no longer needs to receive notifications. In our countdown timer example if observers do not call unregister when they become invisible, they will receive notifications and waste CPU cycles updating invisible UI elements. This issue is called Lapsed Listener Problem.
If you implement the observer pattern in C++ (no garbage collection, memory is managed manually) and you call delete on one of the observers without unregistering it from the subject then you are in trouble. Now you have a dangling pointer that points to invalid data. Whenever the subject notifies its observers a segmentation fault will be raised.
Always unregister your listeners unless they are interested in receiving notifications.
Another approach for dealing with lapsed listeners (observers) is to use Weak References.
--
subject holds a strong reference to the observers:
subject会持有observers的强引用,保证他们一直处于活跃的状态,因为被强引用,如果不进行移除,那么observer就会阻止GC进行垃圾回收处理,直到你将observer从subject中移除,如果这个对象不再被使用了,那么他又无法被GC回收,这时候就产生了memory leak内存泄漏。
Lapsed Listener,当subject改变后,他会通知所有注册的observer,但有些observer在当前可能已经处于invisible的状态,比如UI中,当前状态,UI被隐藏了,那么即便是按收到通知,用户也是看不到的,从合理性的角度,也不应该处理,毕竟他已经处于隐藏的状态,这种就叫Lapsed Listener无效的监听,如果这样的observer有多个,就是在消耗CPU。
针对这种情况,当对象处于invisible时,我们应该手动移除observer,当对象重新处于visible时,我们再register注册它。
类似于下面这样的代码:
image.png这是建议使用的方式,配对出现。
另外一种方式是采用WeakReference弱引用:
uses weak references to keep track of registered observers. If the only references to an instance are weak references, then the instance becomes a candidate for Garbage Collection. In contrast to strong references, weak references don’t prevent garbage collector to finilize the instance and reclaim its memory.
弱引用不会阻止垃圾回收器进行处理,当他不再会引用的时候,就会被垃圾回收器回收掉。
daling pointer悬空指针:
当我们移除Observer时,不要简单的直接的delete掉它,而是应该从当前的字典或是List移除。
因为Observer可能也注册了其它的Subject,在观察其它的Subject的改变通知,在C++这种没有垃圾回收机制,内存需要手动管理的语言中,直接删除Observer会产生悬空指针,即指向了一个不存在的地址,这样就会造成内存泄露,并且实际调用的时候,空指针,导致crush出现。
参考文献:
http://mark-dot-net.blogspot.com/2012/10/understanding-and-avoiding-memory-leaks.html
https://www.grokkingandroid.com/use-contentobserver-to-listen-to-changes/
https://www.jetbrains.com/help/dotmemory/How_to_Find_a_Memory_Leak.html
https://www.red-gate.com/simple-talk/dotnet/.net-framework/understanding-garbage-collection-in-.net/
http://www.andymcm.com/dotnetfaq.htm#5.8
http://ilkinulas.github.io/development/general/2016/04/17/observer-pattern.html
网友评论