觀察者模式 Observer 發佈訂閱模式 源 監聽 行爲型 設計模式(二十三)

觀察者模式 Observer
image_5c1af1b9_5671

意圖

定義對象一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴他的對象都獲得通知並自動更新。
別名:依賴(Dependents),發佈訂閱(Publish-Subscribe)源-監聽(Source-Listener)
 
image_5c1af1b9_431a
《Hold On, We're Going Home》是加拿大說唱歌手德雷克與製做組合Majid Jordan合做的節奏布魯斯歌曲
第一句「I got my eyes on you」就是「我一直關注你」
 
I got my eyes on you,
You're everything that I see
I want your hot love and emotion, endlessly
I can't get over you,
You left your mark on me......
 
電視劇中,也是常常出現」觀察、監視「,好比《黎明以前》
是劉江執導2010年出品的諜戰劇,由吳秀波、林永健、陸劍民、海清等領銜主演。豆瓣評分高達9.2。
有一段周漢亭被盯梢的橋段,有負責門口監視的,有負責電話彙報狀況的,有負責指揮的....他的一舉一動都在敵人的監視以內,引起無數個探子連鎖行動。
 
歌詞中由於喜歡妹子因此持續關注,妹子是目標,歌手是觀察者。
電視劇中敵人爲了破壞我黨工做,因此監視,周漢亭是目標,衆多探子是觀察者。
經過對目標的觀察,觀察者能夠得到事物的動向狀況,進而作出進一步的行動。這就是觀察。
 
在程序中,也常常會出現這種場景
一個系統中必然由多個相互協做的類組成,很常見的問題就是維持狀態的一致性或者說聯動效果
觀察者模式就是爲了解決這種問題,處理「協做者」之間的一致性與聯動問題
 
好比,數據庫中對某個字段進行了更新,可能須要同步修改其餘字段
 
好比,執行一個main方法,IDE的窗口的聯動效果,以下圖所示,點擊run執行後
底部狀態欄會顯示Build狀態,很快完成後就開始運行,側邊欄顯示運行狀態,而後控制檯打印輸出信息
這是一系列的聯動效果
image_5c1af1b9_6655
好比,同一份數據有多種圖表展現,若是數據發生變化,每一個圖表都須要發生變化
image_5c1af1b9_1bae

結構

假設目標爲Subject ,觀察者爲Observer(一個或者多個)
最簡單的實現方式,就是Subject直接調用Observer的方法。
image_5c1af1b9_4e72
當事件發生後,直接調用Observer的相關方法。
僞代碼以下
Subject內使用List維護觀察者
當事件發生,也就是方法f()中,循環通知觀察者
省略了觀察者的維護工做,也就是添加和刪除
class Subject{
List<Observer> observerList = new ArrayList<>();
void f(){
//do sth...
for(Observer o:observerList){
//調用相關方法
o.doSthElse();
}
}

 

依賴倒置原則中,要求應該面向抽象進行編程,而不是面向細節
上面的結構中,不論是目標仍是觀察者的擴展都不方便,因此抽象提取。
image_5c1af1b9_6dec
 
這就是觀察者模式的基本結構。
抽象觀察者角色Observer
爲全部的具體的觀察者定義一個接口,獲得主題的通知信息後,進行同步響應。
通常包含一個方法叫作update()用以同步響應
抽象主題角色Subject
主題角色把全部觀察者對象保存在集合中,提供管理工做,添加和刪除
而且,提供通知工做,也就是調用相關觀察者的update
具體主題角色ConcreteSubject
實現抽象主題接口協議,當狀態方式發生變化時,對觀察者進行通知
具體觀察者角色ConcreteObserver
實現抽象觀察者定義的接口,完成自身相關的同步更新活動

代碼示例

抽象觀察者角色,提供統一的更新方法
package observer;
public interface Observer {
void update();
}
抽象的Subject,內部使用List<Observer>保存觀察者對象
提供了attach和dettach方法用於添加和刪除觀察者
而且提供了通知方法,就是遍歷調用Observer
package observer;
import java.util.LinkedList;
import java.util.List;
 
public abstract class Subject {
 
    List<Observer> observerList;
     
    Subject() {
        observerList = new LinkedList<>();
    }
     
    void attach(Observer o) {
        observerList.add(o);
    }
     
    void dettach(Observer o) {
        observerList.remove(o);
    }
     
    void notifyObservers() {
        for (Observer o : observerList) {
            o.update();
        }
    }
}
具體的主題角色,他有一個行動方法,行動後通知其餘的觀察者
觀察者在父類中列表裏面定義
package observer;
public class ConcreteSubject extends Subject {
    public void action() {
        System.out.println("下雨了");
        super.notifyObservers();
    }
}
具體的觀察者,實現具體的行動
package observer;
public class ConcreateObserver implements Observer {
    @Override
    public void update() {
        System.out.println("那我收衣服了");
    }
}
測試代碼
package observer;
public class Test {
public static void main(String[] args) {
        Observer o1 = new ConcreateObserver();
        ConcreteSubject subject = new ConcreteSubject();
        subject.attach(o1);
        subject.action();
    }
}

 

image_5c1af1b9_179e
若是新增長一個觀察者
package observer;
public class ConcreteObserver1 implements Observer {
    @Override
    public void update() {
        System.out.println("那我得把窗戶關上");
    }
}

 

測試代碼
image_5c1af1ba_3e32
如上圖所示,有兩個觀察者(好比張三和李四),當包租婆大喊一聲下雨了以後,他們都獲得通知
張三去收衣服了,李四把本身的窗戶關上了
 
以上就是觀察者模式的簡單示例。
 
觀察者模式的核心在於對於觀察者的管理和維護,以及發送通知。
消息的發佈訂閱,在程序中就是消息發佈者調用訂閱者的相關方法
觀察者模式將發佈者與訂閱者進行解耦,再也不是直接的方法調用,經過引入Observer角色,完成了發佈者與具體訂閱者之間的解耦
也是一種形式的「方法調用」的解耦

Java的觀察者模式

image_5c1af1ba_413f
 
觀察者接口Observer
是一個interface,定義了一個update方法
void update(Observable o, Object arg);
接受兩個參數,一個是被觀察對象,一個是參數
被觀察角色Observerable 等同於前文Subject
內部使用Vector維護觀察者Observer
提供了對觀察者的管理相關方法,添加、刪除、計算個數、刪除、刪除全部等
 
Observerable內部使用標誌位記錄是否已經發生變化
    private boolean changed = false;
 
發生變更後,設置爲true,通知後設置爲false
image_5c1af1ba_7fcf
 
addObserver(Observer o) 添加指定觀察者
deleteObserver(Observer o)  刪除指定觀察者
deleteObservers()      刪除全部觀察者
countObservers 返回觀察者個數
notifyObservers()
notifyObservers(Object arg) 
通知全部觀察者
一個無參,一個有參
參數傳遞給Observer的update
 
Observerable的實現原理很簡單:
  • 使用Vector保存觀察者,提供了添加、刪除、刪除所有的方法,而且能夠返回觀察者的個數。
  • 若是的確發生變更,將會通知全部的觀察者
提供了兩個版本的notifyObservers,一個有參,有個無參,參數對應update方法的第二個參數
image_5c1af1ba_6594
 
通知後,將會清除變更狀態
 
image_5c1af1ba_7878
Observable中Vector<Observer>是私有的,也並無提供訪問器,只是能夠添加、刪除、或者清除全部
因此子類並不知道具體的Observer
image_5c1af1ba_38a4
代碼示例
package observer.java;
import java.util.Observable;
public class Subject extends Observable {
    public void changeState() {
        System.out.println("下雨了........");
        setChanged();
        notifyObservers();
    }
}

 

package observer.java;
import java.util.Observable;
import java.util.Observer;
public class Watcher1 implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("被觀察者:" + o + " ,參數 " + arg + " ,觀察者1");
    }
}

 

 

package observer.java;
import java.util.Observable;
import java.util.Observer;
public class Watcher2 implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("被觀察者:" + o + " ,參數 " + arg + " ,觀察者2");
    }
}
image_5c1af1ba_7d48
 
Subject 繼承Observable類,自定義了changeState()方法
在方法中,調用
    setChanged();
    notifyObservers();
完成狀態改變和通知。
 
從打印信息能夠看得出來
update方法接收到的第一個參數,就是咱們的被觀察者對象
第二個參數能夠用來封裝傳遞信息
 
因此在java中,除非場景特殊,你又不須要本身寫觀察者模式了,已經內置了
經過繼承和實現相應的類和接口便可。
 
GOF的設計模式出版於95,JDK 1.0始於1996,因此,Java自然支持某些設計模式也很正常
並且,設計模式是經驗總結,GOF將他們概括總結使之廣爲人知,可是並不表明這些經驗前所未有
JDK的開發者人家自己就有這些「經驗」也不足爲奇。

與中介者模式區別

觀察者模式用於一對多依賴場景中的解耦,經過引入Observer角色,將消息發佈者與具體的訂閱者進行解耦
中介者模式是將系統內部多對多的複雜耦合關係,藉助於中介者進行解耦,將網狀結構,簡化爲星型結構,將多對多轉換爲一對多 
 
他們都可以實現消息發佈者與接收者的解耦,消息發佈者都不知道具體的消息接收者
發起消息的Colleague同事類角色是被觀察者,中介類Mediator是觀察者,調用其餘的同事類進行協做
 
儘管觀察者模式強調「一致性通訊」
中介者模式強調「內部組件協做」
可是根本仍是方法執行時,須要同步調用其餘對象
 
兩個模式之間是一種相似的關係,在有些場景可替代轉換。
若是協做關係比較簡單,能夠實現爲一對多的形式,使用觀察者模式
若是協做關係更加複雜,那麼就可使用中介者模式

總結

觀察者模式是在一對多的依賴場景中,對消息發佈者和消息訂閱者的解耦
在觀察者和被觀察者之間創建了一個抽象的耦合,而不是強關聯
經過引入觀察者角色,發佈者不依賴具體的觀察者,從而Subject和Observer能夠獨立發展
被觀察者僅僅知道有N個觀察者,他們以集合的形式被管理,都是Observer角色,可是徹底不知道具體的觀察者
 
觀察者模式的支持廣播,被觀察者會向全部的觀察者發送消息。
 
增長新的觀察者時,不須要修改客戶端代碼,只須要擴展Observer接口便可,知足開閉原則
 
一個觀察者,也多是一個被觀察者,若是出現觀察者調用鏈,將會發生多米諾骨牌效應不斷地進行調用,後果多是難以預知的。
因此要謹慎識別雙重身份的對象,也就是便是觀察者也是被觀察者的對象。
 
被觀察者不知道具體的觀察者,也更不可能知道觀察者具體的行爲
當發生狀態變化時,被觀察者一個小舉動,可能引發很大的性能消耗,而被觀察者對此絕不知情,可能仍舊覺得雲淡風輕。
 
當一個對象狀態的改變,須要同時改變其餘對象時,能夠考慮觀察者模式
當一個對象必須通知其餘人時,可是他又不知道究竟是誰時,能夠考慮觀察者模式
或者將一個抽象模型中的兩個關聯部分解耦,以便獨立發展,提升複用性,解耦不表示斷開,仍舊須要聯繫,能夠藉助於觀察者模式進行聯繫
總之
觀察模式經過引入觀察者角色,將調用者與被調用者解耦,經過觀察者角色聯繫。
但凡相似「廣播」「發佈訂閱」的場景,均可以考慮是否可用。
相關文章
相關標籤/搜索