【架構—設計模式】觀察者模式

在軟件系統中,有些對象之間存在相似交通訊號燈和汽車之間的關係,一個對象的狀態或行爲的變化將致使其餘對象的狀態或行爲也發生改變,它們之間將產生聯動,正所謂「觸一而牽百發」。爲了更好地描述對象之間存在的這種一對多(包括一對一)的聯動,觀察者模式應運而生,它定義了對象之間一種一對多的依賴關係,讓一個對象的改變可以影響其餘對象。php

概述

觀察者模式是使用頻率最高的設計模式之一,它用於創建一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其餘對象,其餘對象將相應做出反應。在觀察者模式中,發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者,一個觀察目標能夠對應多個觀察者,並且這些觀察者之間能夠沒有任何相互聯繫,能夠根據須要增長和刪除觀察者,使得系統更易於擴展。編程

觀察者模式定義以下:設計模式

觀察者模式(Observer Pattern):定義對象之間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆獲得通知並被自動更新。觀察者模式的別名包括髮布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式是一種對象行爲型模式。

觀察者模式結構中一般包括觀察目標和觀察者兩個繼承層次結構,其結構如圖所示:編程語言

1341501815_4830.jpg

在觀察者模式結構圖中包含以下幾個角色:ui

  • Subject(目標):目標又稱爲主題,它是指被觀察的對象。在目標中定義了一個觀察者集合,一個觀察目標能夠接受任意數量的觀察者來觀察,它提供一系列方法來增長和刪除觀察者對象,同時它定義了通知方法notify()。目標類能夠是接口,也能夠是抽象類或具體類。
  • ConcreteSubject(具體目標):具體目標是目標類的子類,一般它包含有常常發生改變的數據,當它的狀態發生改變時,向它的各個觀察者發出通知;同時它還實現了在目標類中定義的抽象業務邏輯方法(若是有的話)。若是無須擴展目標類,則具體目標類能夠省略。
  • Observer(觀察者):觀察者將對觀察目標的改變作出反應,觀察者通常定義爲接口,該接口聲明瞭更新數據的方法update(),所以又稱爲抽象觀察者。
  • ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用,它存儲具體觀察者的有關狀態,這些狀態須要和具體目標的狀態保持一致;它實現了在抽象觀察者Observer中定義的update()方法。一般在實現時,能夠調用具體目標類的attach()方法將本身添加到目標類的集合中或經過detach()方法將本身從目標類的集合中刪除。

觀察者模式描述瞭如何創建對象與對象之間的依賴關係,以及如何構造知足這種需求的系統。觀察者模式包含觀察目標和觀察者兩類對象,一個目標能夠有任意數目的與之相依賴的觀察者,一旦觀察目標的狀態發生改變,全部的觀察者都將獲得通知。做爲對這個通知的響應,每一個觀察者都將監視觀察目標的狀態以使其狀態與目標狀態同步,這種交互也稱爲發佈-訂閱(Publish-Subscribe)。觀察目標是通知的發佈者,它發出通知時並不須要知道誰是它的觀察者,能夠有任意數目的觀察者訂閱它並接收通知。this

下面經過示意代碼來對該模式進行進一步分析。首先咱們定義一個抽象目標Subject,典型代碼以下所示:spa

abstract class Subject
{
    /**
     * 定義一個觀察者集合用於存儲全部觀察者對象
     * @var Observer[]
     */
    protected $observers = [];

    //註冊方法,用於向觀察者集合中增長一個觀察者
    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    //註銷方法,用於在觀察者集合中刪除一個觀察者
    public function detach(Observer $observer)
    {
        unset($this->observers[array_search($observer, $this->observers)]);
    }

    //聲明抽象通知方法
    abstract public function notify();
}

具體目標類ConcreteSubject是實現了抽象目標類Subject的一個具體子類,其典型代碼以下所示:設計

class concreteSubject extends Subject
{
    //實現通知方法
    public function notify()
    {
        foreach ($this->observers as $observer)
        {
            $observer->update();
        }
    }
}

抽象觀察者角色通常定義爲一個接口,一般只聲明一個update()方法,爲不一樣觀察者的更新(響應)行爲定義相同的接口,這個方法在其子類中實現,不一樣的觀察者具備不一樣的響應方法。抽象觀察者Observer典型代碼以下所示:3d

interface Observer
{
    //聲明響應方法
    public function update();
}

在具體觀察者ConcreteObserver中實現了update()方法,其典型代碼以下所示:code

class ConcreteObserver implements Observer
{
    //實現響應方法
    public function update()
    {

    }
}

在有些更加複雜的狀況下,具體觀察者類ConcreteObserver的update()方法在執行時須要使用到具體目標類ConcreteSubject中的狀態(屬性),所以在ConcreteObserver與ConcreteSubject之間有時候還存在關聯或依賴關係,在ConcreteObserver中定義一個ConcreteSubject實例,經過該實例獲取存儲在ConcreteSubject中的狀態。若是ConcreteObserver的update()方法不須要使用到ConcreteSubject中的狀態屬性,則能夠對觀察者模式的標準結構進行簡化,在具體觀察者ConcreteObserver和具體目標ConcreteSubject之間無須維持對象引用。若是在具體層具備關聯關係,系統的擴展性將受到必定的影響,增長新的具體目標類有時候須要修改原有觀察者的代碼,在必定程度上違反了「開閉原則」,可是若是原有觀察者類無須關聯新增的具體目標,則系統擴展性不受影響。

案例

Sunny軟件公司欲開發一款多人聯機對戰遊戲(相似魔獸世界、星際爭霸等遊戲),在該遊戲中,多個玩家能夠加入同一戰隊組成聯盟,當戰隊中某一成員受到敵人攻擊時將給全部其餘盟友發送通知,盟友收到通知後將做出響應。

Sunny軟件公司開發人員須要提供一個設計方案來實現戰隊成員之間的聯動。

Sunny軟件公司開發人員經過對系統功能需求進行分析,發如今該系統中戰隊成員之間的聯動過程能夠簡單描述以下:

聯盟成員受到攻擊-->發送通知給盟友-->盟友做出響應

若是按照上述思路來設計系統,因爲聯盟成員在受到攻擊時須要通知他的每個盟友,所以每一個聯盟成員都須要持有其餘全部盟友的信息,這將致使系統開銷較大,所以Sunny公司開發人員決定引入一個新的角色——「戰隊控制中心」——來負責維護和管理每一個戰隊全部成員的信息。當一個聯盟成員受到攻擊時,將向相應的戰隊控制中心發送求助信息,戰隊控制中心再逐一通知每一個盟友,盟友再做出響應。

爲了實現對象之間的聯動,Sunny軟件公司開發人員決定使用觀察者模式來進行多人聯機對戰遊戲的設計,其基本結構如圖所示:

1341503929_8319.jpg

AllyControlCenter充當目標類,ConcreteAllyControlCenter充當具體目標類,Observer充當抽象觀察者,Player充當具體觀察者。完整代碼以下所示:

<?php

//抽象觀察類
interface Observer
{
    public function getName(): string;

    public function setName(string $name): void;

    //聲明支援盟友方法
    public function help(): void;

    //聲明遭受攻擊方法
    public function beAttacked(AllyControlCenter $acc): void;
}

//戰隊成員類:具體觀察者類
class Player implements Observer
{
    /**
     * @var string
     */
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }

    //支援盟友方法的實現
    public function help(): void
    {
        echo $this->name . ' is on the way to save you';
    }

    //遭受攻擊方法的實現,當遭受攻擊時將調用戰隊控制中心類的通知方法notifyObserver()來通知盟友
    public function beAttacked(AllyControlCenter $acc): void
    {
        echo $this->name . ' is under attacked';
        $acc->notifyObserver($this->name);
    }
}

//戰隊控制中心類:目標類
abstract class AllyControlCenter
{
    /**
     * 戰隊名稱
     * @var string
     */
    protected $allyName;

    /**
     * 定義一個集合用於存儲戰隊成員
     * @var Observer[]
     */
    protected $players = [];

    /**
     * @param string $allyName
     */
    public function setAllyName(string $allyName): void
    {
        $this->allyName = $allyName;
    }

    /**
     * @return string
     */
    public function getAllyName(): string
    {
        return $this->allyName;
    }

    //註冊方法
    public function join(Observer $obs): void
    {
        echo $obs->getName() . ' join the \'' . $this->allyName . '\' ally';
        $this->players[] = $obs;
    }

    //註銷方法
    public function quit(Observer $obs): void
    {
        echo $obs->getName() . ' quit the \'' . $this->allyName . '\' ally';
        unset($this->players[array_search($obs, $this->players)]);
    }

    //聲明抽象通知方法
    abstract public function notifyObserver(string $name): void;
}

//具體戰隊控制中心類:具體目標類
class ConcreteAllyControlCenter extends AllyControlCenter
{
    public function __construct(string $allyName)
    {
        echo $allyName . ' is established';
        $this->allyName = $allyName;
    }

    //實現通知方法
    public function notifyObserver(string $name): void
    {
        echo $this->allyName . ' notify: ' . $name . ' is under attacked';
        //遍歷觀察者集合,調用每個盟友(本身除外)的支援方法
        foreach ($this->players as $player) {
            if ($player->getName() != $name) {
                $player->help();
            }
        }
    
    }
}

在本實例中,實現了兩次對象之間的聯動,當一個遊戲玩家Player對象的beAttacked()方法被調用時,將調用AllyControlCenter的notifyObserver()方法來進行處理,而在notifyObserver()方法中又將調用其餘Player對象的help()方法。Player的beAttacked()方法、AllyControlCenter的notifyObserver()方法以及Player的help()方法構成了一個聯動觸發鏈,執行順序以下所示:

Player.beAttacked() --> AllyControlCenter.notifyObserver() -->Player.help()

總結

觀察者模式是一種使用頻率很是高的設計模式,不管是移動應用、Web應用或者桌面應用,觀察者模式幾乎無處不在,它爲實現對象之間的聯動提供了一套完整的解決方案,凡是涉及到一對一或者一對多的對象交互場景均可以使用觀察者模式。觀察者模式普遍應用於各類編程語言的GUI事件處理的實現,在基於事件的XML解析技術(如SAX2)以及Web事件處理中也都使用了觀察者模式。

主要優勢
  1. 觀察者模式能夠實現表示層和數據邏輯層的分離,定義了穩定的消息更新傳遞機制,並抽象了更新接口,使得能夠有各類各樣不一樣的表示層充當具體觀察者角色。
  2. 觀察者模式在觀察目標和觀察者之間創建一個抽象的耦合。觀察目標只須要維持一個抽象觀察者的集合,無須瞭解其具體觀察者。因爲觀察目標和觀察者沒有緊密地耦合在一塊兒,所以它們能夠屬於不一樣的抽象化層次。
  3. 觀察者模式支持廣播通訊,觀察目標會向全部已註冊的觀察者對象發送通知,簡化了一對多系統設計的難度。
  4. 觀察者模式知足「開閉原則」的要求,增長新的具體觀察者無須修改原有系統代碼,在具體觀察者與觀察目標之間不存在關聯關係的狀況下,增長新的觀察目標也很方便。
主要缺點
  1. 若是一個觀察目標對象有不少直接和間接觀察者,將全部的觀察者都通知到會花費不少時間。
  2. 若是在觀察者和觀察目標之間存在循環依賴,觀察目標會觸發它們之間進行循環調用,可能致使系統崩潰。
  3. 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
適用場景
  1. 一個抽象模型有兩個方面,其中一個方面依賴於另外一個方面,將這兩個方面封裝在獨立的對象中使它們能夠各自獨立地改變和複用。
  2. 一個對象的改變將致使一個或多個其餘對象也發生改變,而並不知道具體有多少對象將發生改變,也不知道這些對象是誰。
  3. 須要在系統中建立一個觸發鏈,A對象的行爲將影響B對象,B對象的行爲將影響C對象……,可使用觀察者模式建立一種鏈式觸發機制。
相關文章
相關標籤/搜索