PHP設計模式之訪問者模式

  • 2019 年 11 月 10 日
  • 筆記

PHP設計模式之訪問者模式

訪問者,就像我們去別人家訪問,或者別人來我們家看望我們一樣。我們每個人都像是一個實體,而來訪的人都會一一的和我們打招呼。畢竟,我們中華民族是非常講究禮數和好客的民族。訪問者是GoF23個設計模式中最複雜的一個模式,也是各類設計模式教材都放在最後的一個模式。先不管難度如何,我們先看看它的定義和實現。

Gof類圖及解釋

GoF定義:表示一個作用於某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作

GoF類圖

程式碼實現

interface Visitor  {      public function VisitConcreteElementA(ConcreteElementA $a);      function VisitConcreteElementB(ConcreteElementB $b);  }    class ConcreteVisitor1 implements Visitor  {      public function VisitConcreteElementA(ConcreteElementA $a)      {          echo get_class($a) . "被" . get_class($this) . "訪問", PHP_EOL;      }      public function VisitConcreteElementB(ConcreteElementB $b)      {          echo get_class($b) . "被" . get_class($this) . "訪問", PHP_EOL;      }  }    class ConcreteVisitor2 implements Visitor  {      public function VisitConcreteElementA(ConcreteElementA $a)      {          echo get_class($a) . "被" . get_class($this) . "訪問", PHP_EOL;      }      public function VisitConcreteElementB(ConcreteElementB $b)      {          echo get_class($b) . "被" . get_class($this) . "訪問", PHP_EOL;      }  }

抽象的訪問者介面及兩個具體實現。可以看作是一家小兩口來我們家作客咯!

interface Element  {      public function Accept(Visitor $v);  }    class ConcreteElementA implements Element  {      public function Accept(Visitor $v)      {          $v->VisitConcreteElementA($this);      }      public function OperationA()      {        }  }    class ConcreteElementB implements Element  {      public function Accept(Visitor $v)      {          $v->VisitConcreteElementB($this);      }      public function OperationB()      {        }  }

元素抽象及實現,也可以看作是要訪問的實體。當然就是我和我媳婦啦。

class ObjectStructure  {      private $elements = [];        public function Attach(Element $element)      {          $this->elements[] = $element;      }        public function Detach(Element $element)      {          $position = 0;          foreach ($this->elements as $e) {              if ($e == $element) {                  unset($this->elements[$position]);                  break;              }              $position++;          }      }        public function Accept(Visitor $visitor)      {          foreach ($this->elements as $e) {              $e->Accept($visitor);          }      }    }

這是一個對象結構,用於保存元素實體並進行訪問調用。大家在客廳里見面,互相寒暄嘛,這裡就是個客廳

$o = new ObjectStructure();  $o->Attach(new ConcreteElementA());  $o->Attach(new ConcreteElementB());    $v1 = new ConcreteVisitor1();  $v2 = new ConcreteVisitor2();    $o->Accept($v1);  $o->Accept($v2);

客戶端的調用,總算讓大家正式見面了,互相介紹握手。一次訪問就愉快的完成了。

  • 讓訪問者調用指定的元素。這裡需要注意的,訪問者調用元素的行為一般是固定的,很少會改變的。也就是VisitConcreteElementA()、VisitConcreteElementB()這兩個方法。也就是定義對象結構的類很少改變,但經常需要在此結構上定義新的操作時,會使用訪問者模式
  • 需要對一個對象結構中的對象進行很多不同的並且不相關的操作,而你想避免讓這些操作「污染」這些對象的類時,適用於訪問者模式
  • 訪問者模式適合數據結構不變化的情況。所以,它是一種平常你用不上,但一旦需要的時候就只能用這種模式的模式。GoF:「大多時候你並不需要訪問者模式,但當一旦你需要訪問者模式時,那就是真的需要它了」。因為很少有數據結構不發生變化的情況
  • 訪問者模式的一些優缺點:易於增加新的操作;集中相關的操作而分離無關的操作;增加新的ConcreteElement類很困難;通過類層次進行訪問;累積狀態;破壞封裝

我們公司的賬務,只有收入和支出兩項(Element),但是不同的部門(Visitor)訪問的時候會給出不同的內容。比如我查看的時候只需要查看每月或每季度的匯總數據即可,財務總監則需要詳細的收支記錄,而會計在做賬時更是需要完整的明細。可見,公司的運營還真的是需要非常廣泛的知識的,不僅是管理能力,賬務知識也是必要了解的內容!!

完整程式碼:https://github.com/zhangyue0503/designpatterns-php/blob/master/23.visitor/source/visitor.php

實例

最後一個模式的例子還是回到我們的資訊發送上來。同樣的還是多個服務商,它們作為訪問者需要去使用各自的簡訊發送及APP推送介面。這時,就可以使用訪問者模式來進行操作,實現這些訪問者的全部操作。

訪問者模式資訊發送

完整源碼:https://github.com/zhangyue0503/designpatterns-php/blob/master/23.visitor/source/visitor-msg.php

<?php    interface ServiceVisitor  {      public function SendMsg(SendMessage $s);      function PushMsg(PushMessage $p);  }    class AliYun implements ServiceVisitor  {      public function SendMsg(SendMessage $s)      {          echo '阿里雲發送簡訊!', PHP_EOL;      }      public function PushMsg(PushMessage $p)      {          echo '阿里雲推送資訊!', PHP_EOL;      }  }    class JiGuang implements ServiceVisitor  {      public function SendMsg(SendMessage $s)      {          echo '極光發送簡訊!', PHP_EOL;      }      public function PushMsg(PushMessage $p)      {          echo '極光推送簡訊!', PHP_EOL;      }  }    interface Message  {      public function Msg(ServiceVisitor $v);  }    class PushMessage implements Message  {      public function Msg(ServiceVisitor $v)      {          echo '推送腳本啟動:';          $v->PushMsg($this);      }  }    class SendMessage implements Message  {      public function Msg(ServiceVisitor $v)      {          echo '簡訊腳本啟動:';          $v->SendMsg($this);      }  }    class ObjectStructure  {      private $elements = [];        public function Attach(Message $element)      {          $this->elements[] = $element;      }        public function Detach(Message $element)      {          $position = 0;          foreach ($this->elements as $e) {              if ($e == $element) {                  unset($this->elements[$position]);                  break;              }              $position++;          }      }        public function Accept(ServiceVisitor $visitor)      {          foreach ($this->elements as $e) {              $e->Msg($visitor);          }      }    }    $o = new ObjectStructure();  $o->Attach(new PushMessage());  $o->Attach(new SendMessage());    $v1 = new AliYun();  $v2 = new JiGuang();    $o->Accept($v1);  $o->Accept($v2);

說明

  • 我們假定發送簡訊和發送推送是不變的兩個行為,也就是它們倆的數據結構是穩定不變的
  • 這樣我們就可以方便的增加ServiceVisitor,當增加百度雲或者別的什麼簡訊提供商時,就很方便的增加訪問者就可以了
  • 訪問者模式比較適合數據結構穩定的結構。比如帳單只有收入支出、人的性別只有男女等