一文帶你徹底搞懂發(fā)布與訂閱設(shè)計
本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲 。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。
一、介紹
我們常說的發(fā)布訂閱設(shè)計模式,也叫觀察者模式,也就是事件監(jiān)聽機制,觀察者模式訂閱了一種一對多的依賴關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象,當這個主題對象發(fā)生改變時,會通知所有的觀察者對象,使他們能夠自動的更新自己!
一個軟件系統(tǒng)要求某個對象在發(fā)生變化時,某些其他的對象作出相應的改變,能做到這點的設(shè)計方案有很多,但觀察者模式是滿足這一要求的各種設(shè)計方案中最重要的一種。
從整體來看,觀察者模式所涉及的角色有:
- 抽象主題角色:抽象主題角色把所有對觀察者對象的引用保存在一個集合中,每個主題都可以有任意數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象;
- 具體主題角色:將有關(guān)狀態(tài)存入具體觀察者對象,在具體主題的內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知;
- 抽象觀察者角色:為所有的具體觀察者提供一個接口,在得到主題通知時更新自己;
- 具體觀察者角色:存儲與主題的狀態(tài)相關(guān)的狀態(tài)。具體觀察者角色實現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)協(xié)調(diào)
廢話也不多說了,下面我們直接講案例!
二、示例
抽象主題角色,有增加觀察者、刪除觀察者、通知觀察者的功能,內(nèi)容如下:
- public abstract class AbstractSubject {
- /** 用來保存注冊的觀察者對象 */
- private List<Observer> list = new ArrayList<Observer>();
- /**
- * 注冊觀察者對象
- */
- public void add(Observer observer){
- list.add(observer);
- System.out.println("add an observer");
- }
- /**
- * 刪除觀察者對象
- * @param observer
- */
- public void remove(Observer observer){
- list.remove(observer);
- System.out.println("delete an observer");
- }
- /**
- * 通知所有注冊的觀察者對象
- * @param state
- */
- public void notifyObservers(String state){
- for (int i = 0; i < list.size(); i++) {
- list.get(i).update(state);
- }
- }
- }
具體主題角色,這個change方法放在子類中是因為可能不同的主題在改變觀察者狀態(tài)的時候會做一些不同的操作,因此就不統(tǒng)一放在父類Subject里面了,內(nèi)容如下:
- public class ConcreteSubject extends AbstractSubject{
- private String state;
- public void change(String newState){
- state = newState;
- System.out.println("主題狀態(tài):" + state);
- //狀態(tài)發(fā)生改變,通知所有的觀察者
- super.notifyObservers(state);
- }
- }
觀察者接口,內(nèi)容如下:
- public interface Observer {
- /**
- * 修改狀態(tài)
- * @param state
- */
- void update(String state);
- }
具體觀察者實現(xiàn)了觀察者接口,內(nèi)容如下:
- public class ConcreteObserver implements Observer{
- @Override
- public void update(String state) {
- System.out.println("觀察者,收到狀態(tài):" + state);
- }
- }
客戶端調(diào)用代碼,一旦主題調(diào)用了change方法改變觀察者的狀態(tài),那么觀察者Observer里面的observerState全都改變了,內(nèi)容如下:
- public class ObserverClient {
- public static void main(String[] args) {
- //創(chuàng)建一個主題角色
- ConcreteSubject subject = new ConcreteSubject();
- //創(chuàng)建觀察者對象
- ConcreteObserver observer = new ConcreteObserver();
- //將觀察者加入主題對象上
- subject.add(observer);
- //改變主題狀態(tài)
- subject.change("hello world");
- }
- }
運行結(jié)果如下:
- add an observer
- 主題狀態(tài):hello world
- 觀察者,收到狀態(tài):hello world
這里只添加了一個觀察者,有興趣的可以試試看多添加幾個觀察者,效果都是一樣的,主題角色改變狀態(tài),觀察者狀態(tài)全變。
三、應用
觀察者模式的兩種模型
- 推模型:主題對象向觀察者推送主題的詳細信息,不管觀察者是否需要。推送的信息通常是主題對象的全部或部分數(shù)據(jù),上面的例子就是典型的推模型。
- 拉模型:主題對象在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到主題對象中去獲取,相當于是觀察者從主題對象中拉數(shù)據(jù)。一般這種模型的實現(xiàn)中,會把主題對象自身通過update()方法傳遞給觀察者,這樣觀察者在需要獲取數(shù)據(jù)的時候,就可以通過這個引用來獲取了。
兩種模型的比較
- 1、推模型是假設(shè)主題對象知道觀察者需要的數(shù)據(jù),拉模型是假設(shè)主題對象不知道觀察者需要什么數(shù)據(jù),干脆把自身傳遞過去,讓觀察者自己按需要取值。
- 2、推模型可能會使得觀察者對象難以復用,因為觀察者的update()方法是按需要定義的參數(shù),可能無法兼顧到?jīng)]有考慮到的使用情況,這意味著出現(xiàn)新的情況時,可能要提供新的update()方法。
JDK是有直接支持觀察者模式的,就是java.util.Observer這個接口,內(nèi)容如下:
- public interface Observer {
- /**
- * This method is called whenever the observed object is changed. An
- * application calls an <tt>Observable</tt> object's
- * <code>notifyObservers</code> method to have all the object's
- * observers notified of the change.
- *
- * @param o the observable object.
- * @param arg an argument passed to the <code>notifyObservers</code>
- * method.
- */
- void update(Observable o, Object arg);
- }
這就是觀察者的接口,定義的觀察者只需要實現(xiàn)這個接口就可以了。update()方法,被觀察者對象的狀態(tài)發(fā)生變化時,被觀察者的notifyObservers()方法就會調(diào)用這個方法,內(nèi)容如下:
- public class Observable {
- private boolean changed = false;
- private Vector obs;
- /** Construct an Observable with zero Observers. */
- public Observable() {
- obs = new Vector();
- }
- /**
- * Adds an observer to the set of observers for this object, provided
- * that it is not the same as some observer already in the set.
- * The order in which notifications will be delivered to multiple
- * observers is not specified. See the class comment.
- *
- * @param o an observer to be added.
- * @throws NullPointerException if the parameter o is null.
- */
- public synchronized void addObserver(Observer o) {
- if (o == null)
- throw new NullPointerException();
- if (!obs.contains(o)) {
- obs.addElement(o);
- }
- }
- ...
- }
這是被觀察者的父類,也就是主題對象。這是一個線程安全的類,是基于Vector實現(xiàn)的。
創(chuàng)建一個觀察者,內(nèi)容如下:
- import java.util.Observable;
- import java.util.Observer;
- public class Watched implements Observer {
- @Override
- public void update(Observable o, Object arg) {
- System.out.println("觀察者,收到狀態(tài):" + arg);
- }
- }
創(chuàng)建一個主題,內(nèi)容如下:
- import java.util.Observable;
- public class Subject extends Observable {
- private String data;
- public void setData(String newData){
- System.out.println("主題狀態(tài):" + newData);
- data = newData;
- setChanged();
- notifyObservers(data);
- }
- }
寫一個main函數(shù)調(diào)用,內(nèi)容如下:
- public class WatchedClient {
- public static void main(String[] args) {
- //創(chuàng)建觀察者對象
- Watched watched = new Watched();
- //創(chuàng)建主題
- Subject subject = new Subject();
- //將觀察者對象加入主題
- subject.addObserver(watched);
- //修改主題狀態(tài)
- subject.setData("hello world");
- }
- }
運行結(jié)果,內(nèi)容如下:
- 主題狀態(tài):hello world
- 觀察者,收到狀態(tài):hello world
看到主題對象改變的時候,觀察者對象的狀態(tài)也隨之改變。
四、總結(jié)
引入設(shè)計模式最主要的作用我認為就是兩點:
- 去重復代碼,使得代碼更清晰、更易讀、更易擴展
- 解耦,使得代碼可維護性更好,修改代碼的時候可以盡量少改地方
使用觀察者模式可以很好地做到這兩點。增加觀察者,直接new出觀察者并注冊到主題對象之后就完事了,刪除觀察者,主題對象調(diào)用方法刪除一下就好了,其余都不用管。主題對象狀態(tài)改變,內(nèi)部會自動幫我們通知每一個觀察者,是不是很方便呢?
觀察者模式主要應用場景有:
- 對一個對象狀態(tài)的更新需要其他對象同步更新
- 對象僅需要將自己的更新通知給其他對象而不需要知道其他對象的細節(jié),如消息推送