PHP設計模式之觀察者模式

zmxyzmxy1234發表於2020-07-31

使用場景

假設專案經理讓我們寫了一個登陸介面,咔咔擦擦寫完了

  • 第二天讓我們加入統計登陸次數,然後在後面加程式碼
  • 第三天讓我們判斷登陸地區,又在後面加程式碼
  • 第四天讓我們在使用者登陸後推送活動,再再後面加程式碼
  • 第N天,這個介面已經雜亂到沒人想維護了

我們需要讓專案保持高內聚低耦合,就可以用到觀察者模式(也不是非要,看需求)

概念

觀察者,觀察者,首先要有個被人觀察的角色,這是唯一的,然後會有無數個觀察者去看她,可以說是一群人在圍觀一個人,既然有無數個觀眾,那總得有個東西記錄有哪些觀察者,那就應該有一個類似於陣列一樣來儲存所有觀察者,總結就是一個被觀察者,無數個觀察者,再有一個容器記錄

程式碼示例

  • 介面示例
    // 主題介面
    interface Subject{
      public function register(Observer $observer);
      public function notify();
    }
    // 觀察者介面
    interface Observer{
      public function watch();
    }
    Subject就是被觀察者,Observer就是觀眾,也就是觀察者

被觀察者

// 被觀察者
class Action implements Subject{
     public $_observers=array();
     public function register(Observer $observer){
         $this->_observers[]=$observer;
     }

     public function notify(){
         foreach ($this->_observers as $observer) {
             $observer->watch();
         }

     }
 }

Action實現了被觀察者介面,他現在就是被觀察者,再定義一個$_observers陣列,他就是記錄觀眾的容器了。
首先實現register方法,用它傳入一個觀察者,然後塞到陣列裡,再實現notify()方法,它會遍歷容器陣列,執行每個觀察者的watch()方法。

觀察者

// 觀察者
class Cat implements Observer{
     public function watch(){
         echo "Cat watches TV<hr/>";
     }
 }
 class Dog implements Observer{
     public function watch(){
         echo "Dog watches TV<hr/>";
     }
 }
 class People implements Observer{
     public function watch(){
         echo "People watches TV<hr/>";
     }
 }

這裡定義了三個觀察者,全都實現了Observer介面,前面的Subject會迴圈呼叫每個觀察者的watch()方法,所以我們需要實現每個觀察者的watch()方法。

呼叫

 // 應用例項
$action=new Action();
$action->register(new Cat());
$action->register(new People());
$action->register(new Dog());
$action->notify();

首先new被觀察者物件,執行它的register()方法,把每個觀察者都放入容器陣列,最後執行notify()方法,通知所有觀察者執行自己的方法。

PHP原生自帶的觀察者模式

PHP有自帶的觀察者模式

  • splsubject介面 - 被觀察者
  • Observer介面 - 觀察者
  • SplObjectStorage物件 - 容器

首先我們有一個使用者登入類

class user{

  public function login()
  {
      echo '登入完畢'
  }

讓他實現splsubject介面成為被觀察者。

  • 首先在建構函式里,讓他new SplObjectStorag()物件並賦值到屬性上方便後面呼叫

  • 實現attach()方法,用來註冊觀察者

  • 實現detach()方法,用來刪除觀察者

  • 實現notify()方法,用來遍歷容器,呼叫每個觀察者的update方法(必須是update)

  • rewind方法是容器指標重置到最開始,valid方法檢測容器是否遍歷完成並返回布林,current方法是獲取當前的觀察者,next方法是將指標後移一位

  • 修改login()方法,在裡面呼叫notify()來通知觀察者事件完成了

    class user implements splsubject{
    
      protected $observer = null;
    
      public function __construct()
      {
          $this->observer = new SplObjectStorage();
      }
    
      public function login()
      {
          $this->notify();
          echo '登入完畢';
      }
    
      public function attach(SplObserver $observer)
      {
          $this->observer->attach($observer);
      }
    
      public function detach(SplObserver $observer)
      {
          $this->observer->detach($observer);
      }
    
      public function notify()
      {
          $this->observer->rewind();
          while ($this->observer->valid())
          {
              $observer = $this->observer->current();
              $observer->update($this);
              $this->observer->next();
          }
      }
    }

觀察者

每個觀察者實現SplObserver介面,並實現update()方法

class cat implements SplObserver {

    public function update(SplSubject $subject)
    {
        echo '小貓叫一下';
    }
}
class dog implements SplObserver {
    public function update(SplSubject $subject)
    {
        echo '小狗吼一聲';
    }
}

應用

// 實時觀察
$user = new user();
$user->attach(new cat());
$user->attach(new dog());
$user->login();
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章