隔牆有耳的觀察者模式(Observer Patern)

來源:互聯網
上載者:User
登入系統想必大家都做過,驗證使用者名稱密碼就登入成功,日誌系統應該記錄此次登入,如果登入出錯,安全系統應該會記錄此次錯誤,郵件系統也應該會發送相關郵件給管理員,等等。這就好像登入系統被很多人監視一樣,一旦有什麼風吹草動,立即會被其它系統獲悉。那就用觀察者模式來試試,類圖如下:


很簡單的模式,實現代碼:

Php代碼

<?php  interface Observable{      function attach( Observer $observer );      function detach( Observer $observer );      function notify();  }      class login implements Observable{      const LOGIN_USER_UNKNOW = 1;      const LOGIN_WRONG_PASS = 2;      const LOGIN_ACCESS = 3;      private $status = array();      private $observers = array();        public function setStatus( $status, $user, $ip ) {          $this->status = array( $status, $user, $ip );      }      public function getStatus() {          return $this->status;      }      public function handleLogin( $user, $pass, $ip ) {          switch ( mt_rand( 1, 3 ) ) {          case 1:              $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip );              $ret = false;              break;          case 2:              $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip );              $ret = false;              break;          case 3:              $this->setStatus( self::LOGIN_ACCESS, $user, $ip );              $ret = true;              break;          }          $this->notify();          return $ret;      }          public function attach( Observer $observer ) {          $this->observers[] = $observer;      }        public function detach( Observer $observer ) {          $newObservers = array();          foreach ( $this->observers as $obs ) {              if ( $obs !== $observer )                  $newObservers[] = $obs;          }          $this->observers = $newObservers;      }        public function notify() {          foreach ( $this->observers as $obs ) {              $obs->update( $this );          }      }  }    interface Observer{      function update( Observable $observable );  }    class SecurityMonitor implements Observer{      function update( Observable $observable ) {          $status = $observable->getStatus();          if($status[0] == Login::LOGIN_WRONG_PASS){              echo __CLASS__.":".$status[1]."於".$status[2]."登入失敗";          }      }  }    $login = new Login();  $login->attach(new SecurityMonitor());  $login->handleLogin('XXX','XXX','127.0.0.1');  ?>


出錯時的運行結果:

SecurityMonitor:XXX於127.0.0.1登入失敗[Finished in 0.1s]

代碼中可以看到login對象主動添加SecurityMonitor對象觀察。這樣要調用Login::getStatus(),SecurityMonitor類就必須瞭解更多資訊。雖然調用發生於一個ObServable對象上,但無法保證對象也是一個Login對象。為解決這個問題,有一個辦法:斷續保持ObServable介面的通用性,由ObServer類負責保證它們的主體是正確的類型。它們甚至能將自己添加到主體上。類圖如下:


實現代碼如下:

Php代碼

<?php  interface Observable{      function attach( Observer $observer );      function detach( Observer $observer );      function notify();  }      class login implements Observable{      const LOGIN_USER_UNKNOW = 1;      const LOGIN_WRONG_PASS = 2;      const LOGIN_ACCESS = 3;      private $status = array();      private $observers = array();        public function setStatus( $status, $user, $ip ) {          $this->status = array( $status, $user, $ip );      }      public function getStatus() {          return $this->status;      }      public function handleLogin( $user, $pass, $ip ) {          switch ( mt_rand( 1, 3 ) ) {          case 1:              $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip );              $ret = false;              break;          case 2:              $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip );              $ret = false;              break;          case 3:              $this->setStatus( self::LOGIN_ACCESS, $user, $ip );              $ret = true;              break;          }          $this->notify();          return $ret;      }          public function attach( Observer $observer ) {          $this->observers[] = $observer;      }        public function detach( Observer $observer ) {          $newObservers = array();          foreach ( $this->observers as $obs ) {              if ( $obs !== $observer )                  $newObservers[] = $obs;          }          $this->observers = $newObservers;      }        public function notify() {          foreach ( $this->observers as $obs ) {              $obs->update( $this );          }      }  }    interface Observer{      function update( Observable $observable );  }  //以上代碼和上例是一樣的  abstract class LoginObserver implements Observer{      private $login;      public function __construct( Login $login ) {          $this->login = $login;          $login->attach( $this );      }        public function update( Observable $observable ) {          if ( $this->login === $observable )              $this->doUpdate( $observable );      }      abstract function doUpdate( Login $login );  }    class SecurityMonitor extends LoginObserver{      public function doUpdate( Login $login ) {          $status = $login->getStatus();          if ( $status[0] == Login::LOGIN_WRONG_PASS )              echo __CLASS__.":".$status[1]."於".$status[2]."登入失敗";      }  }    $login = new Login();  new SecurityMonitor($login);//<strong>此外login對象是被動被觀察的</strong>  $login->handleLogin( 'XXX', 'XXX', '127.0.0.1' );  ?>

運行結果與上例子相同

在php5後,內建的SPL擴充提供了對觀察者模式的原生支援。將上例子通過SPL改進後:

Php代碼

<?php  class login implements SplSubject{      const LOGIN_USER_UNKNOW = 1;      const LOGIN_WRONG_PASS = 2;      const LOGIN_ACCESS = 3;      private $status = array();      // private $observers = array();      private $storage;      public function __construct() {          $this->storage = new SplObjectStorage();      }      public function setStatus( $status, $user, $ip ) {          $this->status = array( $status, $user, $ip );      }      public function getStatus() {          return $this->status;      }      public function handleLogin( $user, $pass, $ip ) {          switch ( mt_rand( 1, 3 ) ) {          case 1:              $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip );              $ret = false;              break;          case 2:              $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip );              $ret = false;              break;          case 3:              $this->setStatus( self::LOGIN_ACCESS, $user, $ip );              $ret = true;              break;          }          $this->notify();          return $ret;      }          public function attach( SplObserver $observer ) {          $this->storage->attach( $observer );      }        public function detach( SplObserver $observer ) {          $this->storage->detach( $observer );      }        public function notify() {          foreach ( $this->storage as $obs ) {              $obs->update( $this );          }      }  }    abstract class LoginObserver implements SplObserver{      private $login;      public function __construct( Login $login ) {          $this->login = $login;          $login->attach( $this );      }        public function update( SplSubject $subject ) {          if ( $this->login === $subject )              $this->doUpdate( $subject );      }      abstract function doUpdate( Login $login );  }    class SecurityMonitor extends LoginObserver{      public function doUpdate( Login $login ) {          $status = $login->getStatus();          if ( $status[0] == Login::LOGIN_WRONG_PASS )              echo __CLASS__.":".$status[1]."於".$status[2]."登入失敗";      }  }    $login = new Login();  new SecurityMonitor( $login );  $login->handleLogin( 'XXX', 'XXX', '127.0.0.1' );  ?>

代碼都寫完了,還是要懂點理論的。

觀察者模式的定義

定義對象間一種一對多的依賴關係,使得每當一個對象改變狀態,則所有依賴於它的對象都會得到通知並被自動更新。觀察者模式由四種角色構成:

1、Subject被觀察者

定義被觀察者必須實現的職責,它必須能夠動態地增加、取消觀察者。它一般是抽象類別或者是實作類別,僅僅完成作為被觀察者必須實現的職責,管理觀察者並通過觀察者。

2、Observer觀察者

觀察者接收到訊息後,即進行update操作,對接收到的資訊進行處理。

3、ConcreteSubject具體的被觀察者

定義被觀察者自己的商務邏輯,同時定義對哪些事件進行通知。

4、ConcreteObserver具體的觀察者

每個觀察在接收到訊息後的處理反應是不同,各個觀察者有自己的處理邏輯。

觀察者模式的優點

1、觀察者和被觀察者之間是抽象耦合

如此設計,則不管是增加觀察者還是被觀察者都非常容易擴充,而且在java、php中都已經實現的抽象層級的定義,在系統擴充方面更是得心應手。

2、建立一套觸發機制

根據單一職責原則,每個類的職責是單一的,那麼怎麼把各個單一的職責串聯成真實世界的複雜的邏輯關係呢?觀察者模式可以完美地實現這裡的鏈條形式

觀察者模式的缺點

觀察者模式需要考慮一下開發效率和運行效率的問題,一個被觀察者,多個觀察者,開發和調試就會比較複雜,而且在php中訊息的通知是順序執行,一個觀察者卡殼,會影響整體的執行效率。在這種情況下,一般多考慮非同步方式。多級觸發時的效率更是讓人擔憂,設計時注意。

觀察者模式的使用情境

1、關聯線為情境。需要注意的是,關係行為是可拆分的,而不是“組合”關係

2、事件多級觸發情境

3、跨系統的訊息交換情境,如訊息佇列的處理機制

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.