本文源碼見:https://github.com/get-set/get-designpatterns/tree/master/observer
觀察者(Observer)模式定義了一種一對(duì)多的依賴關(guān)系杈湾,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象惕稻,主體對(duì)象的狀態(tài)變化會(huì)通知所有觀察者對(duì)象鹉梨。觀察者模式又叫做發(fā)布-訂閱(Publish/Subscribe)模式曼月、模型-視圖(Model/View)模式槽卫、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式案疲。
這種模式在我們實(shí)際生活中并不鮮見属提,比如訂牛奶西采、訂報(bào)紙。我們訂閱了某報(bào)紙之后鞠柄,一旦報(bào)紙有新版出來侦高,就會(huì)送到我們報(bào)箱或手中,去過取消訂閱厌杜,那么也就再也收不到了奉呛。有了互聯(lián)網(wǎng)之后,無論是微博好友還是微信訂閱號(hào)夯尽,我們都可以“關(guān)注”和“取消關(guān)注”瞧壮,關(guān)注了就可以收到信息推動(dòng)。這些都是觀察者模式的現(xiàn)實(shí)體現(xiàn)匙握。
例子
以微信訂閱號(hào)的關(guān)注和消息推送為例咆槽。
無論是微信的訂閱號(hào)、微博的大V圈纺、喜馬拉雅的音頻專輯秦忿,都可以被關(guān)注或取消關(guān)注,當(dāng)有新的文章蛾娶、消息灯谣、音頻作品出現(xiàn)的時(shí)候,訂閱了的粉絲都會(huì)收到消息蛔琅。所以我們可以抽象出來一個(gè)共同的抽象類Publisher
來實(shí)現(xiàn)這些公共的方法:
Publisher.java
public abstract class Publisher {
private List<Subscriber> funs = new ArrayList<Subscriber>();
private String message;
public void publishMessage(String message) {
this.message = message;
notifyAllSubscribers();
}
public String getMessage() {
return message;
}
public void addSubscriber(Subscriber subscriber) {
funs.add(subscriber);
}
public void delSubscriber(Subscriber subscriber) {
funs.remove(subscriber);
}
public void notifyAllSubscribers() {
Iterator<Subscriber> it = funs.iterator();
while (it.hasNext()) {
it.next().update(this);
}
}
}
可以看到胎许,Publisher
維護(hù)有一個(gè)訂閱者的集合,當(dāng)有新的內(nèi)容更新時(shí)(其中message
統(tǒng)一表示文章罗售、信息或聲音作品等各種形式的內(nèi)容辜窑,由publishMessage
更新內(nèi)容),會(huì)調(diào)用notifyAllSubscibers
方法來通知所有關(guān)注人寨躁。
這里要注意的是谬擦,這種通知是一種回調(diào)行為,也就是通過遍歷并調(diào)用各個(gè)subscriber
的update
方法來進(jìn)行通知朽缎。我們?cè)賮砜匆幌?code>Subscriber:
Subscriber.java
public interface Subscriber {
void update(Publisher publisher);
}
可見Subscriber
只規(guī)定了一種方法惨远,那就是被回調(diào)的update
谜悟。
具體來說,微信訂閱號(hào)就是一種Publisher
:
WeixinPublisher.java
public class WeixinPublisher extends Publisher {
private String dingyuehao;
public WeixinPublisher(String dingyuehao) {
this.dingyuehao = dingyuehao;
}
@Override
public String toString() {
return "微信訂閱號(hào)[" + dingyuehao + "]";
}
}
而普通的微信用戶——即關(guān)注者——就是Subscriber
(通過繼承實(shí)現(xiàn)):
WeixinAccount.java
public class WeixinAccount implements Subscriber {
private String accountName;
public WeixinAccount(String accountName) {
this.accountName = accountName;
}
public void update(Publisher publisher) {
System.out.println(accountName + "的微信收到了來自" + publisher + "的推送文章: " + publisher.getMessage());
}
}
微信用戶實(shí)現(xiàn)了具體的update
方法北秽,定義了在收到通知后要做哪些操作葡幸,比如閱讀、轉(zhuǎn)發(fā)等等贺氓,這里通過打印一行文字來表示蔚叨。
我們來看一下效果:
Client.java
public class Client {
public static void main(String[] args) {
WeixinPublisher publisher = new WeixinPublisher("享學(xué)IT");
publisher.addSubscriber(new WeixinAccount("張三"));
publisher.addSubscriber(new WeixinAccount("李四"));
publisher.addSubscriber(new WeixinAccount("王五"));
publisher.publishMessage("Java設(shè)計(jì)模式百例-觀察者模式");
}
}
張三或李四執(zhí)行“關(guān)注”操作后,微信訂閱號(hào)執(zhí)行addSubscriber
操作將他們添加到自己的訂閱者名單中辙培,當(dāng)發(fā)布新消息時(shí)蔑水,訂閱者都可以收到,我們看一下輸出:
張三的微信收到了來自微信訂閱號(hào)[享學(xué)IT]的推送文章: Java設(shè)計(jì)模式百例-觀察者模式
李四的微信收到了來自微信訂閱號(hào)[享學(xué)IT]的推送文章: Java設(shè)計(jì)模式百例-觀察者模式
王五的微信收到了來自微信訂閱號(hào)[享學(xué)IT]的推送文章: Java設(shè)計(jì)模式百例-觀察者模式
總結(jié)
例子看完后扬蕊,用一個(gè)類圖“鳥瞰”一下類和接口關(guān)系就比較清晰了:
抱歉搀别,這個(gè)類關(guān)系圖的布局不是很直觀,但是有幾個(gè)觀察者模式的特點(diǎn)是可以總結(jié)出來的:
- 觀察者模式是一個(gè)一對(duì)多的關(guān)系尾抑,一個(gè)被觀察者對(duì)應(yīng)多個(gè)觀察者歇父,這種關(guān)系通過在被觀察者內(nèi)維護(hù)一個(gè)觀察者的集合來實(shí)現(xiàn)。
- 但是與“被圍觀”不同的是再愈,被觀察者擁有添加和刪除觀察者的方法榜苫,主動(dòng)權(quán)在自己手中。
- 當(dāng)被觀察者狀態(tài)有變動(dòng)時(shí)翎冲,也是由被觀察者主動(dòng)通知自己維護(hù)的“名單”中的各個(gè)觀察者垂睬,通知是采用回調(diào)接口方法的方式。
Java內(nèi)置觀察者模式
由于觀察者模式應(yīng)用廣泛抗悍,Java內(nèi)置了觀察者模式的抽象類和接口:
被觀察者 Observable.java
可以看到其中的關(guān)鍵方法addObserver
羔飞、deleteObserver
和notifyObservers
。這樣檐春,上邊的例子就不用自己寫抽象類Publisher
了,直接使用Observable
即可么伯。
觀察者 Observer.java 接口也是同樣的疟暖,不過update
方法的參數(shù)更加具有普適性:
public interface Observer {
void update(Observable o, Object arg);
}
其中,第二個(gè)參數(shù)arg
是Observable.notifyObservers(Object)
方法的參數(shù)傳過來的內(nèi)容田柔。
使用模式與咱們的例子是一樣的:
偷懶截了《Java與模式》的圖俐巴。
大家在看源碼的時(shí)候如果發(fā)現(xiàn)了類名類似XxxObserver
或XxxListener
這樣的類時(shí),不妨看一下硬爆,有可能就是應(yīng)用了觀察者模式欣舵。