設計模式-- 觀察者模式Observer(物件行為型)

benbenxiongyuan發表於2014-04-10

1.概述

一些物件導向的程式設計方式,提供了一種構建物件間複雜網路互連的能力。當物件們連線在一起時,它們就可以相互提供服務和資訊。

通常來說,當某個物件的狀態發生改變時,你仍然需要物件之間能互相通訊。但是出於各種原因,你也許並不願意因為程式碼環境的改變而對程式碼做大的修改。也許,你只想根據你的具體應用環境而改進通訊程式碼。或者,你只想簡單的重新構造通訊程式碼來避免類和類之間的相互依賴與相互從屬。

2.問題

當一個物件的狀態發生改變時,你如何通知其他物件?是否需要一個動態方案――一個就像允許指令碼的執行一樣,允許自由連線的方案?

3.解決方案

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

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

 

4.適用性

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

5.結構


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;
	}

}
/**
 * 抽象觀摩者
 *
 */
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


相關文章