觀察者模式
Laravel的Event事件系統提供了一個簡單的觀察者模式實現,能夠訂閱和監聽應用中發生的各種事件,在PHP的標準庫(SPL)裡甚至提供了三個介面SplSubject
, SplObserver
, SplObjectStorage
來讓開發者更容易地實現觀察者模式,不過我還是想脫離SPL提供的介面和特定程式語言來說一下如何通過物件導向程式設計來實現觀察者模式,示例是PHP程式碼不過用其他面嚮物件語言實現起來也是一樣的。
模式定義
觀察者模式(Observer Pattern):定義物件間的一種一對多依賴關係,使得每當一個物件狀態發生改變時,其相關依賴物件皆得到通知並被自動更新。觀察者模式又叫做釋出-訂閱(Publish/Subscribe)模式、模型-檢視(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
觀察者模式的核心在於Subject和Observer介面,Subject(主題目標)包含一個給定的狀態,觀察者“訂閱”這個主題,將主題的當前狀態通知觀察者,每次給定狀態改變時所有觀察者都會得到通知。
發生改變的物件稱為觀察目標,而被通知的物件稱為觀察者,一個觀察目標可以對應多個觀察者,而且這些觀察者之間沒有相互聯絡,可以根據需要增加和刪除觀察者,使得系統更易於擴充套件。
模式結構說明
- Subject 目標抽象類
- ConcreteSubject 具體目標
- Observer 觀察者抽象類
- ConcreteObserver 具體觀察者
應用舉例
比如在設定使用者(主題)的狀態後要分別傳送當前的狀態描述資訊給使用者的郵箱和手機,我們可以使用兩個觀察者訂閱使用者的狀態,一旦設定狀態後主題就會通知的訂閱了狀態改變的觀察者,在兩個觀察者裡面我們可以分別來實現傳送郵件資訊和簡訊資訊的功能。
- 抽象目標類
abstract class Subject
{
protected $stateNow;
protected $observers = [];
public function attach(Observer $observer)
{
array_push($this->observers, $observer);
}
public function detach(Observer $ob)
{
$pos = 0;
foreach ($this->observers as $viewer) {
if ($viewer == $ob) {
array_splice($this->observers, $pos, 1);
}
$pos++;
}
}
public function notify()
{
foreach ($this->observers as $viewer) {
$viewer->update($this);
}
}
}
複製程式碼
在抽象類中attach
detach
和notify
都是具體方法,這些是繼承才能使用的方法,將由Subject
的子類使用。
- 具體目標類
class ConcreteSubject extends Subject
{
public function setState($state)
{
$this->stateNow = $state;
$this->notify();
}
public function getState()
{
return $this->stateNow;
}
}
複製程式碼
- 抽象觀察者
abstract class Observer
{
abstract public function update(Subject $subject);
}
複製程式碼
在抽象觀察者中,抽象方法update
等待子類為它提供一個特定的實現。
- 具體觀察者
class ConcreteObserverDT extends Observer
{
private $currentState;
public function update(Subject $subject)
{
$this->currentState = $subject->getState();
echo `<div style="font-size:10px;">`. $this->currentState .`</div>`;
}
}
class ConcreteObserverPhone extends Observer
{
private $currentState;
public function update(Subject $subject)
{
$this->currentState = $subject->getState();
echo `<div style="font-size:20px;">`. $this->currentState .`</div>`;
}
}
複製程式碼
在例子中為了理解起來簡單,我們只是根據不同的客戶端設定了不同的內容樣式,實際應用中可以真正的呼叫郵件和簡訊服務來傳送資訊。
- 使用觀察者模式
class Client
{
public function __construct()
{
$sub = new ConcreteSubject();
$obDT = new ConcreteObserverDT();
$obPhone = new ConcreteObserverPhone();
$sub->attach($obDT);
$sub->attach($obPhone);
$sub->setState(`Hello World`);
}
}
$worker = new Client();
複製程式碼
何時使用觀察者模式
- 一個物件的改變將導致其他一個或多個物件也發生改變,而不知道具體有多少物件將發生改變,可以降低物件之間的耦合度。
- 一個物件必須通知其他物件,而並不知道這些物件是誰。
- 基於事件觸發機制來解耦複雜邏輯時,從整個邏輯的不同關鍵點抽象出不同的事件,主流程只需要關心最核心的邏輯並能正確地觸發事件(Subject),其餘相關功能實現由觀察者或者叫訂閱者來完成。
總結
-
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個目標物件,當這個目標物件的狀態發生變化時,會通知所有觀察者物件,使它們能夠自動更新。
-
模式包含四個角色:目標又稱為主題,它是指被觀察的物件;具體目標是目標類的子類,通常它包含有經常發生改變的資料,當它的狀態發生改變時,向它的各個觀察者發出通知;觀察者將對觀察目標的改變做出反應;在具體觀察者中維護一個指向具體目標物件的引用,它儲存具體觀察者的有關狀態,這些狀態需要和具體目標的狀態保持一致。
-
觀察者模式的主要優點在於可以實現表示層和資料邏輯層的分離,並在觀察目標和觀察者之間建立一個抽象的耦合,支援廣播通訊;其主要缺點在於如果一個觀察目標物件有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間,而且如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰。
本文已經收錄在系列文章Laravel核心程式碼學習裡,歡迎訪問閱讀。