設計模式 ( 十六 ) 觀察者模式Observer(對象行為型)

1.概述

一些面向對象的編程方式,提供了一種構建對象間復雜網絡互連的能力。當對象們連接在一起時,它們就可以相互提供服務和信息。

通常來說,當某個對象的狀態發生改變時,你仍然需要對象之間能互相通信。但是出于各種原因,你也許并不愿意因為代碼環境的改變而對代碼做大的修改。也許,你只想根據你的具體應用環境而改進通信代碼?;蛘?,你只想簡單的重新構造通信代碼來避免類和類之間的相互依賴與相互從屬。

2.問題

當一個對象的狀態發生改變時,你如何通知其他對象?是否需要一個動態方案――一個就像允許腳本的執行一樣,允許自由連接的方案?

3.解決方案

             觀測模式:定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時, 所有依賴于它的對象都得到通知并被自動更新。

觀測模式允許一個對象關注其他對象的狀態,并且,觀測模式還為被觀測者提供了一種觀測結構,或者說是一個主體和一個客體。主體,也就是被觀測者,可以用來聯系所有的觀測它的觀測者??腕w,也就是觀測者,用來接受主體狀態的改變 觀測就是一個可被觀測的類(也就是主題)與一個或多個觀測它的類(也就是客體)的協作。不論什么時候,當被觀測對象的狀態變化時,所有注冊過的觀測者都會得到通知。
觀測模式將被觀測者(主體)從觀測者(客體)種分離出來。這樣,每個觀測者都可以根據主體的變化分別采取各自的操作。(觀測模式和Publish/Subscribe模式一樣,也是一種有效描述對象間相互作用的模式。)觀測模式靈活而且功能強大。對于被觀測者來說,那些查詢哪些類需要自己的狀態信息和每次使用那些狀態信息的額外資源開銷已經不存在了。另外,一個觀測者可以在任何合適的時候進行注冊和取消注冊。你也可以定義多個具體的觀測類,以便在實際應用中執行不同的操作。
將一個系統分割成一系列相互協作的類有一個常見的副作用:需要維護相關對象間的一致性。我們不希望為了維持一致性而使各類緊密耦合,因為這樣降低了它們的可重用性。

4.適用性

在以下任一情況下可以使用觀察者模式:
? 當一個抽象模型有兩個方面, 其中一個方面依賴于另一方面。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用。
? 當對一個對象的改變需要同時改變其它對象 , 而不知道具體有多少對象有待改變。
? 當一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之 , 你不希望這些對象是緊密耦合的。

5.結構

1.jpg

6.模式的組成

觀察者模式包含如下角色:
目標(Subject): 目標知道它的觀察者??梢杂腥我舛鄠€觀察者觀察同一個目標。 提供注冊和刪除觀察者對象的接口。
具體目標(ConcreteSubject):  將有關狀態存入各ConcreteObserver對象。
觀察者(Observer):  為那些在目標發生改變時需獲得通知的對象定義一個更新接口。當它的狀態發生改變時, 向它的各個觀察者發出通知。
具體觀察者(ConcreteObserver):   維護一個指向ConcreteSubject對象的引用。存儲有關狀態,這些狀態應與目標的狀態保持一致。實現O b s e r v e r的更新接口以使自身狀態與目標的狀態保持一致。

7.效果

Observer模式允許你獨立的改變目標和觀察者。你可以單獨復用目標對象而無需同時復用其觀察者, 反之亦然。它也使你可以在不改動目標和其他的觀察者的前提下增加觀察者。
下面是觀察者模式其它一些優點:
1 )觀察者模式可以實現表示層和數據邏輯層的分離,并定義了穩定的消息更新傳遞機制,抽象了更新接口,使得可以有各種各樣不同的表示層作為具體觀察者角色。
2 )在觀察目標和觀察者之間建立一個抽象的耦合 :一個目標所知道的僅僅是它有一系列觀察者 , 每個都符合抽象的Observer類的簡單接口。目標不知道任何一個觀察者屬于哪一個具體的類。這樣目標和觀察者之間的耦合是抽象的和最小的。因為目標和觀察者不是緊密耦合的, 它們可以屬于一個系統中的不同抽象層次。一個處于較低層次的目標對象可與一個處于較高層次的觀察者通信并通知它 , 這樣就保持了系統層次的完整。如果目標和觀察者混在一塊 , 那么得到的對象要么橫貫兩個層次 (違反了層次性), 要么必須放在這兩層的某一層中(這可能會損害層次抽象)。
3) 支持廣播通信 :不像通常的請求, 目標發送的通知不需指定它的接收者。通知被自動廣播給所有已向該目標對象登記的有關對象。目標對象并不關心到底有多少對象對自己感興趣 ;它唯一的責任就是通知它的各觀察者。這給了你在任何時刻增加和刪除觀察者的自由。處理還是忽略一個通知取決于觀察者。
4) 觀察者模式符合“開閉原則”的要求。
觀察者模式的缺點
1) 如果一個觀察目標對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
2) 如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
3) 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。
4)  意外的更新 因為一個觀察者并不知道其它觀察者的存在 , 它可能對改變目標的最終代價一無所知。在目標上一個看似無害的的操作可能會引起一系列對觀察者以及依賴于這些觀察者的那些對象的更新。此外 , 如果依賴準則的定義或維護不當,常常會引起錯誤的更新 , 這種錯誤通常很難捕捉。
      簡單的更新協議不提供具體細節說明目標中什么被改變了 , 這就使得上述問題更加嚴重。如果沒有其他協議幫助觀察者發現什么發生了改變,它們可能會被迫盡力減少改變。

8.實現

在php的SPL支持觀察者模式,SPL 提供了 SplSubject 和 SplObserver 接口。

SplSubject 接口提供了attach()、detach()、notify() 三個方法。而 SplObserver 接口則提供了 update()方法。

SplSubject 派生類維護了一個狀態,當狀態發生變化時 – 比如屬性變化等,就會調用 notify() 方法,這時,之前在 attach() 方法中注冊的所有 SplObserver 實例的 update() 方法就會被調用。接口定義如下:

<?php  
/** 
 * 這一模式的概念是SplSubject類維護了一個特定狀態,當這個狀態發生變化時,它就會調用notify()方法。 
 * 調用notify()方法時,所有之前使用attach()方法注冊的SplObserver實例的update方法都會被調用。 
 * 
 */  
interface SplSubject{  
      public function attach(SplObserver $observer);//注冊觀察者  
      public function detach(SplObserver $observer);//釋放觀察者  
      public function notify();//通知所有注冊的觀察者  
}  
interface SplObserver{  
      public function update(SplSubject $subject);//觀察者進行更新狀態  
}

實現代碼:

<?php  
/** 
 *具體目標 
 * 
 */  
class ConcreteSubject implements SplSubject {  
  private $observers, $value;  
  public function __construct() {  
    $this->observers = array();  
  }  
  
  public function attach(SplObserver $observer) { //注冊觀察者  
    $this->observers[] = $observer;  
  }  
  
  public function detach(SplObserver $observer) { //釋放觀察者  
    if($idx = array_search($observer,$this->observers,true)) {  
      unset($this->observers[$idx]);  
    }  
  }  
  
  public function notify() { //通知所有觀察者  
    foreach($this->observers as $observer) {  
      $observer->update($this);  
    }  
  }  
  
  public function setValue($value) {  
    $this->value = $value;  
    $this->notify();  
  }  
  
  public function getValue() {  
    return $this->value;  
  }  
  
}  
/** 
 * 具體觀察者 
 * 
 */  
class ConcreteObserver1 implements SplObserver {  
  
  public function update(SplSubject $subject) {  
    echo 'ConcreteObserver1  value is ',$subject->getValue(), '<br>';  
  }  
  
}  
/** 
 * 具體觀察者 
 * 
 */  
class ConcreteObserver2 implements SplObserver {  
  
  public function update(SplSubject $subject) {  
    echo 'ConcreteObserver2 value is ', $subject->getValue(), '<br>';  
  }  
  
}  
  
$subject = new ConcreteSubject();  
$observer1 = new ConcreteObserver1();  
$observer2 = new ConcreteObserver2();  
$subject->attach($observer1);  
$subject->attach($observer2);  
$subject->setValue(5);  
?>

我們擴展上面的例子,根據目標狀態而更新不同的觀察者:

<?php    
/** 
 *具體目標  
 *  
 */    
  
class ConcreteSubject implements SplSubject {  
    private $observers, $_state;  
    public function __construct() {  
        $this->observers = array();  
    }  
    /** 
     *  注冊觀察者   
     * 
     * @param SplObserver $observer 
     */  
    public function attach(SplObserver $observer) {  
        $this->observers[] = $observer;  
    }  
    /** 
     *  //釋放觀察者   
     * 
     * @param SplObserver $observer 
     */  
    public function detach(SplObserver $observer) {  
        if($idx = array_search($observer,$this->observers,true)) {  
            unset($this->observers[$idx]);  
        }  
    }  
    /** 
     * 通知所有觀察者   
     *  
     */  
    public function notify() {  
        /** 
        * 只要狀態改變,就通知觀察者 
        */  
        foreach($this->observers as $observer) {  
            if ($observer->getState() == $this->_state) {  
                $observer->update($this);  
            }  
        }  
    }  
    /** 
     * 設置狀態 
     * 
     * @param unknown_type $state 
     */  
    public function setState($state) {  
        $this->_state = $state;  
        $this->notify();  
    }  
  
    public function getState() {  
        return $this->_state;  
    }  
  
}  
/** 
<?php    
/** 
 *具體目標  
 *  
 */    
  
class ConcreteSubject implements SplSubject {  
    private $observers, $_state;  
    public function __construct() {  
        $this->observers = array();  
    }  
    /** 
     *  注冊觀察者   
     * 
     * @param SplObserver $observer 
     */  
    public function attach(SplObserver $observer) {  
        $this->observers[] = $observer;  
    }  
    /** 
     *  //釋放觀察者   
     * 
     * @param SplObserver $observer 
     */  
    public function detach(SplObserver $observer) {  
        if($idx = array_search($observer,$this->observers,true)) {  
            unset($this->observers[$idx]);  
        }  
    }  
    /** 
     * 通知所有觀察者   
     *  
     */  
    public function notify() {  
        /** 
        * 只要狀態改變,就通知觀察者 
        */  
        foreach($this->observers as $observer) {  
            if ($observer->getState() == $this->_state) {  
                $observer->update($this);  
            }  
        }  
    }  
    /** 
     * 設置狀態 
     * 
     * @param unknown_type $state 
     */  
    public function setState($state) {  
        $this->_state = $state;  
        $this->notify();  
    }  
  
    public function getState() {  
        return $this->_state;  
    }  
  
}  
/** 
 * 抽象觀摩者 
 * 
 */  
abstract class bserver{  
    private $_state;  
  
    function __construct($state) {  
        $this->_state = $state;  
    }  
  
    public function setState($state) {  
        $this->_state = $state;  
        $this->notify();  
    }  
      
    public function getState() {  
        return $this->_state;  
    }  
  
}  
/** 
 * 具體觀察者 1 
 *  
 */    
class ConcreteObserver1 extends bserver  implements SplObserver {  
  
    function __construct($state) {  
        parent::__construct($state);  
    }  
    public function update(SplSubject $subject) {  
        echo 'ConcreteObserver1  state is ',$subject->getState(), '<br>';  
    }  
  
}  
/** 
 * 具體觀察者 2 
 *  
 */    
class ConcreteObserver2 extends bserver   implements SplObserver {  
    function __construct($state) {  
        parent::__construct($state);  
    }  
    public function update(SplSubject $subject) {  
        echo 'ConcreteObserver2 state is ', $subject->getState(), '<br>';  
    }  
  
}  
/** 
 * 具體觀察者 3 
 *  
 */    
class ConcreteObserver3 extends bserver   implements SplObserver {  
    function __construct($state) {  
        parent::__construct($state);  
    }  
    public function update(SplSubject $subject) {  
        echo 'ConcreteObserver3 state is ', $subject->getState(), '<br>';  
    }  
  
}  
  
$subject = new ConcreteSubject();  
$observer1 = new ConcreteObserver1(1);  
$observer2 = new ConcreteObserver2(1);  
$observer3 = new ConcreteObserver3(2);  
$subject->attach($observer1);  
$subject->attach($observer2);  
$subject->attach($observer3);  
echo 'Subject state is 1', '<br>';  
$subject->setState(1);  
echo 'Subject state is 2', '<br>';  
$subject->setState(2);  
?>

9.與其他相關模式

1) 終結者模式Mediator: 通過封裝復雜的更新語義 , ChangeManager充當目標和觀察者之間的中介者。
2) 單間模式Singleton: ChangeManager可使用Singleton模式來保證它是唯一的并且是可全局訪問
的。

10.總結與分析

通過Observer模式,把一對多對象之間的通知依賴關系的變得更為松散,大大地提高了程序的可維護性和可擴展性,也很好的符合了開放-封閉原則。

轉自:http://blog.csdn.net/hguisu/article/details/7556625

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2837

(0)
s19930811s19930811
上一篇 2015-07-23 17:48
下一篇 2015-07-24 21:56

相關推薦

  • 8.8作業

    4、如何設置tab縮進為4個字符?    set tabstop=4     5、復制/etc/rc.d/init.d/functions文件至/tmp目錄;替換/tmp/functions文件中的/etc/sysconfig/init為/var/log; cp /etc/rc.d/init.d/functi…

    Linux干貨 2016-08-11
  • rsyslog配置詳解,結合mysql+loganalyzer展現

        環境:Centos7.2 前言:系統日日夜夜不停地運行著,有這么一個守護進程,兢兢業業地不斷記錄它運行產生的日志,有不起眼的閑言碎語,值得管理員撇一眼的系統報錯,也默默地接收來自進程的嚴厲警告,甚至在內核崩潰前夕,同樣不遺余力記錄著當時發生的情形。他是無言的記錄者,沒有特別的修辭,但他的記錄的文字卻擲地有聲。本…

    系統運維 2016-10-25
  • 基于ssl功能實現mysql主從復制

    基于ssl功能實現mysql主從復制         證書準備:                                  CA證書…

    2016-11-22
  • 第八周

    1、寫一個腳本,使用ping命令探測172.16.250.1-172.16.250.254之間的所有主機的在線狀態;      在線的主機使用綠色顯示;      不在線的主使用紅色顯示; #!/bin/bash # for i in 172.16.250.{…

    Linux干貨 2017-05-23
  • Linux命令幫助文檔的使用及簡單命令使用-2016-7-25

    Linux命令幫助文檔的使用   相關命令 whatis    COMMAND –help    man and info 本地幫助文檔/usr/share/doc   在使用系統內建的幫助文檔之前,我們需要了解需要命令幫助是否是內部命令和外部命令…

    Linux干貨 2016-08-04
  • 馬哥教育網絡班22期+第5周課程練習

    1、顯示當前系統上root、fedora或user1用戶的默認shell;cat /etc/passwd | grep "^\<root\>"cat /etc/passwd | grep "^\<fedora\>"cat /etc/passwd | grep "^\<user1\…

    Linux干貨 2016-09-19
欧美性久久久久