每日一文
已不先定,牧人不正,事用不巧,是謂“忘情失道”;己審先定以牧入,策而無形容,莫見其門,是謂“天神”。
觀察者模式
觀察者模式(有時又被稱為發(fā)布-訂閱模式、模型-視圖模式、源-收聽者模式或從屬者模式)是軟件設(shè)計模式的一種。在此種模式中,一個目標(biāo)物件管理所有相依于它的觀察者物件,并且在它本身的狀態(tài)改變時主動發(fā)出通知。這通常透過呼叫各觀察者所提供的方法來實(shí)現(xiàn)。此種模式通常被用來實(shí)作事件處理系統(tǒng)。
類圖
1. 訂閱者模式
- 觀察者實(shí)現(xiàn)Observer接口
package com.example.patternproxy.observable;
import android.util.Log;
import java.util.Observable;
import java.util.Observer;
/**
* Created on 2017/3/14.
* Desc:讀者,訂閱者
* Author:Eric.w
*/
public class Reader implements Observer {
private String name;
public String getName() {
return name;
}
public Reader(String name) {
super();
this.name = name;
}
public void subcreble(String writerName) {
WriterManager.getInstance().getWriter(writerName).addObserver(this);
}
public void unSubcreble(String writerName) {
WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
}
/**
* 被觀察者變化時觸發(fā)的方法(notifyObservers())
*
* @param o
* @param arg
*/
@Override
public void update(Observable o, Object arg) {
if (o instanceof Writer) {
Log.e("pattern", ((Writer) o).getName() + "出新書了,《" + ((Writer) o).getBook() + "》!");
}
}
}
- 被觀察者實(shí)現(xiàn)Observable接口
package com.example.patternproxy.observable;
import android.util.Log;
import java.util.Observable;
/**
* Created on 2017/3/14.
* Desc:作者,可被訂閱
* Author:Eric.w
*/
public class Writer extends Observable {
private String name;
private String book;
public String getBook() {
return book;
}
public String getName() {
return name;
}
public Writer(String name) {
super();
this.name = name;
WriterManager.getInstance().putWriter(this);
}
public void addNewBook(String bookname) {
Log.e("pattern", "public new book :" + bookname);
this.book = bookname;
setChanged();
notifyObservers();
}
}
- 為了更好的管理被訂閱者:增加WriterManager,對數(shù)據(jù)是一個很好的分離
package com.example.patternproxy.observable;
import java.util.HashMap;
/**
* Created on 2017/3/14.
* Desc:作者管理類
* Author:Eric.w
*/
public class WriterManager {
public HashMap<String, Writer> writerMap = new HashMap<>();
public void putWriter(Writer writer) {
writerMap.put(writer.getName(), writer);
}
public Writer getWriter(String writerName) {
return writerMap.get(writerName);
}
/**
* 靜態(tài)內(nèi)部類,的靜態(tài)成員變量只在類加載的時候初始化,直郵調(diào)用了getInstance()
* 才會去加載類創(chuàng)建類的實(shí)例
*/
public WriterManager() {
}
private static class WriterManagerInstance {
private static WriterManager instance = new WriterManager();
}
public static WriterManager getInstance() {
return WriterManagerInstance.instance;
}
}
2. 事件驅(qū)動模式
首先事件驅(qū)動模型與觀察者模式勉強(qiáng)的對應(yīng)關(guān)系可以看成是,被觀察者相當(dāng)于事件源,觀察者相當(dāng)于監(jiān)聽器,事件源會產(chǎn)生事件,監(jiān)聽器監(jiān)聽事件。所以這其中就攙和到四個類,事件源,事件,監(jiān)聽器以及具體的監(jiān)聽器。
import java.util.HashSet;
import java.util.Set;
//作者類
public class Writer{
private String name;//作者的名稱
private String lastNovel;//記錄作者最新發(fā)布的小說
private Set<WriterListener> writerListenerList = new HashSet<WriterListener>();//作者類要包含一個自己監(jiān)聽器的列表
public Writer(String name) {
super();
this.name = name;
WriterManager.getInstance().add(this);
}
//作者發(fā)布新小說了,要通知所有關(guān)注自己的讀者
public void addNovel(String novel) {
System.out.println(name + "發(fā)布了新書《" + novel + "》!");
lastNovel = novel;
fireEvent();
}
//觸發(fā)發(fā)布新書的事件,通知所有監(jiān)聽這件事的監(jiān)聽器
private void fireEvent(){
WriterEvent writerEvent = new WriterEvent(this);
for (WriterListener writerListener : writerListenerList) {
writerListener.addNovel(writerEvent);
}
}
//提供給外部注冊成為自己的監(jiān)聽器的方法
public void registerListener(WriterListener writerListener){
writerListenerList.add(writerListener);
}
//提供給外部注銷的方法
public void unregisterListener(WriterListener writerListener){
writerListenerList.remove(writerListener);
}
public String getLastNovel() {
return lastNovel;
}
public String getName() {
return name;
}
}
import java.util.EventListener;
public interface WriterListener extends EventListener{
void addNovel(WriterEvent writerEvent);
}
public class Reader implements WriterListener{
private String name;
public Reader(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
//讀者可以關(guān)注某一位作者,關(guān)注則代表把自己加到作者的監(jiān)聽器列表里
public void subscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).registerListener(this);
}
//讀者可以取消關(guān)注某一位作者,取消關(guān)注則代表把自己從作者的監(jiān)聽器列表里注銷
public void unsubscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).unregisterListener(this);
}
public void addNovel(WriterEvent writerEvent) {
Writer writer = writerEvent.getWriter();
System.out.println(name+"知道" + writer.getName() + "發(fā)布了新書《" + writer.getLastNovel() + "》,非要去看!");
}
}
首先本來是實(shí)現(xiàn)Observer接口,現(xiàn)在要實(shí)現(xiàn)WriterListener接口,響應(yīng)的update方法就改為我們定義的addNovel方法,當(dāng)中的響應(yīng)基本沒變。另外就是關(guān)注和取消關(guān)注的方法中,原來是給作者類添加觀察者和刪除觀察者,現(xiàn)在是注冊監(jiān)聽器和注銷監(jiān)聽器,幾乎是沒什么變化的。
我們徹底將剛才的觀察者模式改成了事件驅(qū)動,現(xiàn)在我們使用事件驅(qū)動的類再運(yùn)行一下客戶端,其中客戶端代碼和WriterManager類的代碼是完全不需要改動的,直接運(yùn)行客戶端即可。我們會發(fā)現(xiàn)得到的結(jié)果與觀察者模式一模一樣。
走到這里我們發(fā)現(xiàn)二者可以達(dá)到的效果一模一樣,那么兩者是不是一樣呢?
答案當(dāng)然是否定的,首先我們從實(shí)現(xiàn)方式上就能看出,事件驅(qū)動可以解決觀察者模式的問題,但反過來則不一定,另外二者所表達(dá)的業(yè)務(wù)場景也不一樣,比如上述例子,使用觀察者模式更貼近業(yè)務(wù)場景的描述,而使用事件驅(qū)動,從業(yè)務(wù)上講,則有點(diǎn)勉強(qiáng)。
二者除了業(yè)務(wù)場景的區(qū)別以外,在功能上主要有以下區(qū)別。
- 觀察者模式中觀察者的響應(yīng)理論上講針對特定的被觀察者是唯一的(說理論上唯一的原因是,如果你愿意,你完全可以在update方法里添加一系列的elseif去產(chǎn)生不同的響應(yīng),但LZ早就說過,你應(yīng)該忘掉elseif),而事件驅(qū)動則不是,因為我們可以定義自己感興趣的事情,比如剛才,我們可以監(jiān)聽作者發(fā)布新書,我們還可以在監(jiān)聽器接口中定義其它的行為。再比如tomcat中,我們可以監(jiān)聽servletcontext的init動作,也可以監(jiān)聽它的destroy動作。
- 雖然事件驅(qū)動模型更加靈活,但也是付出了系統(tǒng)的復(fù)雜性作為代價的,因為我們要為每一個事件源定制一個監(jiān)聽器以及事件,這會增加系統(tǒng)的負(fù)擔(dān),各位看看tomcat中有多少個監(jiān)聽器和事件類就知道了。
- 另外觀察者模式要求被觀察者繼承Observable類,這就意味著如果被觀察者原來有父類的話,就需要自己實(shí)現(xiàn)被觀察者的功能,當(dāng)然,這一尷尬事情,我們可以使用適配器模式彌補(bǔ),但也不可避免的造成了觀察者模式的局限性。事件驅(qū)動中事件源則不需要,因為事件源所維護(hù)的監(jiān)聽器列表是給自己定制的,所以無法去制作一個通用的父類去完成這個工作。
- 被觀察者傳送給觀察者的信息是模糊的,比如update中第二個參數(shù),類型是Object,這需要觀察者和被觀察者之間有約定才可以使用這個參數(shù)。而在事件驅(qū)動模型中,這些信息是被封裝在Event當(dāng)中的,可以更清楚的告訴監(jiān)聽器,每個信息都是代表的什么。