Java设计模式10:观察者模式
觀察者模式
觀察者模式也叫作發(fā)布-訂閱模式,也就是事件監(jiān)聽機(jī)制。觀察者模式定義了一種一對多的依賴關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象,這個主題對象在狀態(tài)上發(fā)生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。
?
觀察者模式的結(jié)構(gòu)
一個軟件系統(tǒng)常常要求在某一個對象狀態(tài)發(fā)生變化時,某些其他的對象作出相應(yīng)的改變。能做到這一點的設(shè)計方案有很多,但是為了使系統(tǒng)能夠易于復(fù)用,應(yīng)該選擇低耦合度的設(shè)計方案。減少對象之間的耦合有利于系統(tǒng)的復(fù)用,但是同時需要使這些低耦合度的對象之間能夠維持行動的協(xié)調(diào)一致,保證高度的協(xié)作。觀察者模式是滿足這一要求的各種設(shè)計方案中最重要的一種。
觀察者模式所涉及的角色有:
1、抽象主題角色
抽象主題角色把所有對觀察者對象的引用保存在一個集合中,每個主題都可以有任意數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象。
2、具體主題角色
將有關(guān)狀態(tài)存入具體觀察者對象,在具體主題的內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知。
3、抽象觀察者角色
為所有的具體觀察者提供一個接口,在得到主題通知時更新自己
4、具體觀察者角色
存儲與主題的狀態(tài)相關(guān)的狀態(tài)。具體觀察者角色實現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)協(xié)調(diào)
?
觀察者模式實例
抽象主題角色,有增加觀察者、刪除觀察者、通知觀察者的功能:
public abstract class Subject {/** 用來保存注冊的觀察者對象 */private List<Observer> list = new ArrayList<Observer>();/** 注冊觀察者對象 */public void attch(Observer observer){list.add(observer);System.out.println("Attached an observer");}/** 刪除觀察者對象 */public void detach(Observer observer){list.remove(observer);System.out.println("Detached an observer");}/** 通知所有注冊的觀察者對象 */public void notifyObservers(String newState){for (int i = 0; i < list.size(); i++){list.get(i).update(newState);}} }具體主題角色,這個change方法放在子類中是因為可能不同的主題在改變觀察者狀態(tài)的時候會做一些不同的操作,因此就不統(tǒng)一放在父類Subject里面了:
public class ConcreteSubject extends Subject {private String state;public String getState(){return state;}public void change(String newState){state = newState;System.out.println("主題狀態(tài)為:" + state);// 狀態(tài)發(fā)生改變時,通知各個觀察者this.notifyObservers(state);} }觀察者接口:
public interface Observer {void update(String state); }具體觀察者實現(xiàn)了觀察者接口:
public class ConcreteObserver implements Observer {/** 觀察者的狀態(tài) */private String observerState;public void update(String state){/** 更新觀察者的狀態(tài) */observerState = state;System.out.println("狀態(tài)為:" + observerState);} }客戶端調(diào)用代碼,一旦主題調(diào)用了change方法改變觀察者的狀態(tài),那么觀察者Observer里面的observerState全都改變了:
public static void main(String[] args) {/** 創(chuàng)建主題角色 */ConcreteSubject subject = new ConcreteSubject();/** 創(chuàng)建觀察者對象 */Observer observer = new ConcreteObserver();/** 將觀察者注冊到主題對象上 */subject.attch(observer);/** 改變主題對象的狀態(tài) */subject.change("new state"); }運(yùn)行結(jié)果為:
Attached an observer 主題狀態(tài)為:new state 狀態(tài)為:new state這里只添加了一個觀察者,有興趣的可以試試看多添加幾個觀察者,效果都是一樣的,主題角色改變狀態(tài),觀察者狀態(tài)全變。
?
觀察者模式的兩種模型
1、推模型
主題對象向觀察者推送主題的詳細(xì)信息,不管觀察者是否需要。推送的信息通常是主題對象的全部或部分?jǐn)?shù)據(jù),上面的例子就是典型的推模型
2、拉模型
主題對象在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到主題對象中去獲取,相當(dāng)于是觀察者從主題對象中拉數(shù)據(jù)。一般這種模型的實現(xiàn)中,會把主題對象自身通過update()方法傳遞給觀察者,這樣觀察者在需要獲取數(shù)據(jù)的時候,就可以通過這個引用來獲取了。
?
兩種模型的比較
1、推模型是假設(shè)主題對象知道觀察者需要的數(shù)據(jù),拉模型是假設(shè)主題對象不知道觀察者需要什么數(shù)據(jù),干脆把自身傳遞過去,讓觀察者自己按需要取值
2、推模型可能會使得觀察者對象難以復(fù)用,因為觀察者的update()方法是按需要定義的參數(shù),可能無法兼顧到?jīng)]有考慮到的使用情況,這意味著出現(xiàn)新的情況時,可能要提供新的update()方法
?
觀察者模式在Java中的應(yīng)用及解讀
JDK是有直接支持觀察者模式的,就是java.util.Observer這個接口:
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)用這個方法:
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)的。主題對象中有這些方法對觀察者進(jìn)行操作:
| 方 ?法 | 作 ?用 |
| addObserver(Observer o) | 如果觀察者與集合中已有的觀察者不同,則向?qū)ο蟮挠^察者集合中添加此觀察者 |
| clearChanged()、hasChanged()、setChanged() | 這三個方法算是一對,用來標(biāo)記此觀察者對象(主題對象)是否被改變的狀態(tài)的 |
| countObservers() | 返回觀察者對象的數(shù)目 |
| deleteObserver(Observer o) | 從對象的觀察者集合中刪除某個觀察者 |
| deleteObservers() | 清除觀察者列表 |
| notifyObservers()、notifyObservers(Object arg) | 如果本對象有變化則通知所有等級的觀察者,調(diào)用update()方法 |
?
利用JDK支持的主題/觀察者的例子
創(chuàng)建一個觀察者:
public class Watched extends Observable {private String data = "";public String getData(){return data;}public void setData(String data){if (!this.data.equals(data)){this.data = data;setChanged();}notifyObservers();} }創(chuàng)建一個主題:
public class Watcher implements Observer {String data;public Watcher(Observable o){o.addObserver(this);}public String getData(){return data;}public void update(Observable o, Object arg){this.data = ((Watched)o).getData();System.out.println("狀態(tài)發(fā)生改變:" + ((Watched)o).getData());} }寫一個main函數(shù)調(diào)用一下:
public static void main(String[] args) {/** 創(chuàng)建被觀察者對象 */Watched watched = new Watched();/** 創(chuàng)建觀察者對象,并將被觀察者對象登記 */Watcher watcher = new Watcher(watched);/** 給被觀察者狀態(tài)賦值 */watched.setData("start");watched.setData("run");watched.setData("stop"); }運(yùn)行結(jié)果為:
狀態(tài)發(fā)生改變:start 狀態(tài)發(fā)生改變:run 狀態(tài)發(fā)生改變:stop看到主題對象改變的時候,觀察者對象的狀態(tài)也隨之改變
?
觀察者模式的優(yōu)點以及實際應(yīng)用
引入設(shè)計模式最主要的作用我認(rèn)為就是兩點:
1、去重復(fù)代碼,使得代碼更清晰、更易讀、更易擴(kuò)展
2、解耦,使得代碼可維護(hù)性更好,修改代碼的時候可以盡量少改地方
使用觀察者模式可以很好地做到這兩點。增加觀察者,直接new出觀察者并注冊到主題對象之后就完事了,刪除觀察者,主題對象調(diào)用方法刪除一下就好了,其余都不用管。主題對象狀態(tài)改變,內(nèi)部會自動幫我們通知每一個觀察者,是不是很方便呢?
觀察者模式主要應(yīng)用場景有:
1、對一個對象狀態(tài)的更新需要其他對象同步更新
2、對象僅需要將自己的更新通知給其他對象而不需要知道其他對象的細(xì)節(jié),如消息推送
?
轉(zhuǎn)載于:https://www.cnblogs.com/xrq730/p/4908686.html
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Java设计模式10:观察者模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大道至简第四章流于形式的沟通——读后感
- 下一篇: 第一课 第四节 数据转换