前言
我們學習的設計模式分為三類:建立者模式、結構型模式、行為型模式;建立型模式與物件的建立有關;結構型模式處理類或物件的組合;而行為型模式是對類或物件怎樣互動和怎樣分配職責進行描述;
內容:上一篇介紹PHP 設計模式的結構型一篇,這一篇是行為型。包括:責任鏈模式(Chain Of Responsibilities),命令列模式(Command),直譯器模式(Interpreter),迭代器模式(Iterator),中介者模式(Mediator),備忘錄模式(Memento),觀察者模式(Observer),狀態模式(State),策略模式(Strategy),模板方法模式(Template Method),訪問者模式(Visitor),空物件模式(Null Object),規格模式(Specification)
(一)責任鏈模式(Chain Of Responsibilities)
- 定義
建立一個物件鏈來按指定順序處理呼叫。如果其中一個物件無法處理命令,它會委託這個呼叫給它的下一個物件來進行處理,以此類推。
- 解釋例: 晚上去上英語課,為了好開溜坐到了最後一排,哇,前面坐了好幾個漂亮的MM哎,找張紙條,寫上"Hi,可以做我的女朋友嗎?如果不願意請向前傳",紙條就一個接一個的傳上去了,糟糕,傳到第一排的MM把紙條傳給老師了,聽說是個老處女呀,快跑!
- 例項: 檢視指定介面資料是否存在,如果不存在,讀取快取資料.本例只實現兩層鏈
-
例項程式碼實現
abstract class Handler { private $successor = null; public function __construct(Handler $handler = null) { $this->successor = $handler; } final public function handle() { $processed = $this->processing(); if ($processed === null) { // 請求尚未被目前的處理器處理 => 傳遞到下一個處理器。 if ($this->successor !== null) { $processed = $this->successor->handle(); } } return $processed; } abstract protected function processing(); } class HttpInMemoryCacheHandler extends Handler { private $data; public function __construct(array $data, Handler $successor = null) { parent::__construct($successor); $this->data = $data; } protected function processing() { $key = sprintf( '%s?%s', $_SERVER['SERVER_NAME'], $_SERVER['SCRIPT_NAME'] ); if ($_SERVER['REQUEST_METHOD'] == 'GET' && isset($this->data[$key])) { return $this->data[$key]; } return null; } } class SlowDatabaseHandler extends Handler { protected function processing() { return 'Hello World!'; } } $chain = new HttpInMemoryCacheHandler(['localhost?/index.php' => 'Hello In Memory!'],new SlowDatabaseHandler()); echo $chain->handle();
(二)命令列模式(Command)
- 定義
命令模式把一個請求或者操作封裝到一個物件中。命令模式把發出命令的責任和執行命令的責任分割開,委派給不同的物件。命令模式允許請求的一方和傳送的一方獨立開來,使得請求的一方不必知道接收請求的一方的介面,更不必知道請求是怎麼被接收,以及操作是否執行,何時被執行以及是怎麼被執行的。系統支援命令的撤消。分為 命令角色-具體命令角色-客戶角色-請求者角色-接收者角色
- 解釋例: 俺有一個MM家裡管得特別嚴,沒法見面,只好藉助於她弟弟在我們倆之間傳送資訊,她對我有什麼指示,就寫一張紙條讓她弟弟帶給我。這不,她弟弟又傳送過來一個COMMAND,為了感謝他,我請他吃了碗雜醬麵,哪知道他說:"我同時給我姐姐三個男朋友送COMMAND,就數你最小氣,才請我吃麵。"
- 例項: 一個簡單的增加和貼上功能為例,將這兩個命令組成一個巨集命令。以新建複製命令和貼上命令,
然後將其新增到巨集命令中去。 -
例項程式碼實現
interface Command { public function execute(); } interface MacroCommand extends Command { /** * 巨集命令聚集的管理方法,可以刪除一個成員命令 * @param Command $command */ public function remove(Command $command); /** * 巨集命令聚集的管理方法,可以增加一個成員命令 * @param Command $command */ public function add(Command $command); } class DemoMacroCommand implements MacroCommand { private $_commandList; public function __construct() { $this->_commandList = array(); } public function remove(Command $command) { $key = array_search($command, $this->_commandList); if ($key === FALSE) { return FALSE; } unset($this->_commandList[$key]); return TRUE; } public function add(Command $command) { return array_push($this->_commandList, $command); } public function execute() { foreach ($this->_commandList as $command) { $command->execute(); } } } /** * 具體拷貝命令角色 */ class CopyCommand implements Command { private $_receiver; public function __construct(Receiver $receiver) { $this->_receiver = $receiver; } /** * 執行方法 */ public function execute() { $this->_receiver->copy(); } } /** * 具體拷貝命令角色 */ class PasteCommand implements Command { private $_receiver; public function __construct(Receiver $receiver) { $this->_receiver = $receiver; } /** * 執行方法 */ public function execute() { $this->_receiver->paste(); } } /** * 接收者角色 */ class Receiver { /* 接收者名稱 */ private $_name; public function __construct($name) { $this->_name = $name; } /** * 複製方法 */ public function copy() { echo $this->_name, ' do copy action.<br />'; } /** * 貼上方法 */ public function paste() { echo $this->_name, ' do paste action.<br />'; } } /** * 請求者角色 */ class Invoker { private $_command; public function __construct(Command $command) { $this->_command = $command; } public function action() { $this->_command->execute(); } } /** * 客戶端 */ class Client { /** * Main program. */ public static function main() { $receiver = new Receiver('phpppan'); $pasteCommand = new PasteCommand($receiver); $copyCommand = new CopyCommand($receiver); $macroCommand = new DemoMacroCommand(); $macroCommand->add($copyCommand); $macroCommand->add($pasteCommand); $invoker = new Invoker($macroCommand); $invoker->action(); $macroCommand->remove($copyCommand); $invoker = new Invoker($macroCommand); $invoker->action(); } } Client::main();
(三)直譯器模式(Interpreter)
- 定義
給定一個語言, 定義它的文法的一種表示,並定義一個直譯器,該直譯器使用該表示來解釋語言中的句子。角色: 1)環境角色:定義解釋規則的全域性資訊。2)抽象直譯器:定義了部分解釋具體實現,封裝了一些由具體直譯器實現的介面。3)具體直譯器(MusicNote)實現抽象直譯器的介面,進行具體的解釋執行。
- 解釋例: 俺有一個《泡MM真經》,上面有各種泡MM的攻略,比如說去吃西餐的步驟、去看電影的方法等等,跟MM約會時,只要做一個Interpreter,照著上面的指令碼執行就可以了。
- 例項: 中英文解釋
- 例項程式碼實現
//環境角色,定義要解釋的全域性內容 class Expression{ public $content; function getContent(){ return $this->content; } } //抽象直譯器 abstract class AbstractInterpreter{ abstract function interpret($content); } //具體直譯器,實現抽象直譯器的抽象方法 class ChineseInterpreter extends AbstractInterpreter{ function interpret($content){ for($i=1;$i<count($content);$i++){ switch($content[$i]){ case '0': echo "沒有人<br>";break; case "1": echo "一個人<br>";break; case "2": echo "二個人<br>";break; case "3": echo "三個人<br>";break; case "4": echo "四個人<br>";break; case "5": echo "五個人<br>";break; case "6": echo "六個人<br>";break; case "7": echo "七個人<br>";break; case "8": echo "八個人<br>";break; case "9": echo "九個人<br>";break; default:echo "其他"; } } } } class EnglishInterpreter extends AbstractInterpreter{ function interpret($content){ for($i=1;$i<count($content);$i++){ switch($content[$i]){ case '0': echo "This is nobody<br>";break; case "1": echo "This is one people<br>";break; case "2": echo "This is two people<br>";break; case "3": echo "This is three people<br>";break; case "4": echo "This is four people<br>";break; case "5": echo "This is five people<br>";break; case "6": echo "This is six people<br>";break; case "7": echo "This is seven people<br>";break; case "8": echo "This is eight people<br>";break; case "9": echo "This is nine people<br>";break; default:echo "others"; } } } } //封裝好的對具體直譯器的呼叫類,非直譯器模式必須的角色 class Interpreter{ private $interpreter; private $content; function __construct($expression){ $this->content = $expression->getContent(); if($this->content[0] == "Chinese"){ $this->interpreter = new ChineseInterpreter(); }else{ $this->interpreter = new EnglishInterpreter(); } } function execute(){ $this->interpreter->interpret($this->content); } } //中文解釋 $expression = new Expression(); $expression->content = array("Chinese",3,2,4,4,5); $interpreter = new Interpreter($expression); $interpreter->execute(); //英文解釋 $expression = new Expression(); $expression->content = array("English",1,2,3,0,0); $interpreter = new Interpreter($expression); $interpreter->execute();
(四)迭代器模式 (Iterator)
- 定義
迭代器模式 (Iterator),又叫做遊標(Cursor)模式。提供一種方法訪問一個容器(Container)物件中各個元素,而又不需暴露該物件的內部細節。
- 解釋例: 當你需要訪問一個聚合物件,而且不管這些物件是什麼都需要遍歷的時候,就應該考慮使用迭代器模式。另外,當需要對聚集有多種方式遍歷時,可以考慮去使用迭代器模式。迭代器模式為遍歷不同的聚集結構提供如開始、下一個、是否結束、當前哪一項等統一的介面。
-
例項程式碼實現
class sample implements Iterator { private $_items ; public function __construct(&$data) { $this->_items = $data; } public function current() { return current($this->_items); } public function next() { next($this->_items); } public function key() { return key($this->_items); } public function rewind() { reset($this->_items); } public function valid() { return ($this->current() !== FALSE); } // client $data = array(1, 2, 3, 4, 5); $sa = new sample($data); foreach ($sa as $key => $row) { echo $key, ' ', $row, '<br />'; } }
(五)中介者模式(Mediator)
- 定義
中介者模式包裝了一系列物件相互作用的方式,使得這些物件不必相互明顯作用。從而使他們可以鬆散偶合。當某些物件之間的作用發生改變時,不會立即影響其他的一些物件之間的作用。保證這些作用可以彼此獨立的變化。中介者模式將多對多的相互作用轉化為一對多的相互作用。中介者模式將物件的行為和協作抽象化,把物件在小尺度的行為上與其他物件的相互作用分開處理。兩大角色: 1)中介者角色 2)具體物件角色
- 解釋例: *四個MM打麻將,相互之間誰應該給誰多少錢算不清楚了,幸虧當時我在旁邊,按照各自的籌碼數算錢,賺了錢的從我這裡拿,賠了錢的也付給我,一切就OK啦,俺得到了四個MM的電話。**
- 例項: 模擬客戶端請求資料的過程.
-
例項程式碼實現
//中介者角色介面 interface MediatorInterface { //發出響應 public function sendResponse($content); //做出請求 public function makeRequest(); //查詢資料庫 public function queryDb(); } //中介者角色 class Mediator implements MediatorInterface { /** * @var Server */ private $server; /** * @var Database */ private $database; /** * @var Client */ private $client; /** * @param Database $database * @param Client $client * @param Server $server */ public function __construct(Database $database, Client $client, Server $server) { $this->database = $database; $this->server = $server; $this->client = $client; $this->database->setMediator($this); $this->server->setMediator($this); $this->client->setMediator($this); } public function makeRequest() { $this->server->process(); } public function queryDb(): string { return $this->database->getData(); } /** * @param string $content */ public function sendResponse($content) { $this->client->output($content); } } //抽象物件角色 abstract class Colleague { /** * 確保子類不變化。 * * @var MediatorInterface */ protected $mediator; /** * @param MediatorInterface $mediator */ public function setMediator(MediatorInterface $mediator) { $this->mediator = $mediator; } } //客戶端物件 class Client extends Colleague { public function request() { $this->mediator->makeRequest(); } public function output(string $content) { echo $content; } } //資料據物件 class Database extends Colleague { public function getData(): string { return 'World'; } } //伺服器物件 class Server extends Colleague { public function process() { $data = $this->mediator->queryDb(); $this->mediator->sendResponse(sprintf("Hello %s", $data)); } } $client = new Client(); new Mediator(new Database(), $client, new Server()); $client->request();
(六)備忘錄模式(Memento)
- 定義
備忘錄物件是一個用來儲存另外一個物件內部狀態的快照的物件。備忘錄模式的用意是在不破壞封裝的條件下,將一個物件的狀態捉住,並外部化,儲存起來,從而可以在將來合適的時候把這個物件還原到儲存起來的狀態。
1.Originator(發起人):負責建立一個備忘錄Memento,用以記錄當前時刻自身的內部狀態,並可使用備忘錄恢復內部狀態。Originator可以根據需要決定Memento儲存自己的哪些內部狀態。
2.Memento(備忘錄):負責儲存Originator物件的內部狀態,並可以防止Originator以外的其他物件訪問備忘錄。備忘錄有兩個介面:Caretaker只能看到備忘錄的窄介面,他只能將備忘錄傳遞給其他物件。Originator卻可看到備忘錄的寬介面,允許它訪問返回到先前狀態所需要的所有資料。
3.Caretaker(管理者):負責備忘錄Memento,不能對Memento的內容進行訪問或者操作。 - 解釋例: 同時跟幾個MM聊天時,一定要記清楚剛才跟MM說了些什麼話,不然MM發現了會不高興的哦,幸虧我有個備忘錄,剛才與哪個MM說了什麼話我都拷貝一份放到備忘錄裡面儲存,這樣可以隨時察看以前的記錄啦。
- 例項: 設計一個具有記憶歷史狀態的編輯器
-
例項程式碼實現
//編輯器 class Edit { public $act; public $content; //儲存狀態 public function saveState(Caretaker $caretaker) { return $caretaker->add(new Memento($this->act,$this->content)); } //恢復狀態 public function recoveryState(Memento $memento) { $this->act = $memento->act; $this->content = $memento->content; } public function modify($act,$content) { $this->act = $act; $this->content = $content; } public function show() { echo "操作:".$this->act."\r\n"; echo "內容:".$this->content."\r\n"; } } //編輯狀態儲存箱 class Memento { public $act; public $content; public function __construct($act,$content) { $this->act = $act; $this->content = $content; } } //管理類 class Caretaker { public $data = array(); public $pop = array(); public function add(Memento $memento) { $this->data[] = $memento; } //獲取上一個 public function getLast() { $len = count($this->data); if($len < 1) { return; } $lastData = $this->data[$len - 1]; $this->pop[] = array_pop($this->data); return $lastData; } //獲取下一個 public function getNext() { $len = count($this->pop); if($len < 1) { return; } $nextData = $this->pop[$len - 1]; $this->data[] = array_pop($this->pop); return $nextData; } } class Client { public static function main() { $edit = new Edit(); $caretaker = new Caretaker(); $edit->modify('插入abc','abc'); $edit->saveState($caretaker); $edit->modify('插入d','abcd'); $edit->saveState($caretaker); $edit->modify('刪除c','abd'); $edit->saveState($caretaker); $edit->modify('插入e','abde'); //恢復上一個狀態 $edit->recoveryState($caretaker->getLast()); //刪除c $edit->recoveryState($caretaker->getLast()); //插入d $edit->recoveryState($caretaker->getLast()); //插入abc //恢復下一個狀態 $edit->recoveryState($caretaker->getNext()); //插入abc $edit->recoveryState($caretaker->getNext()); //插入d $edit->recoveryState($caretaker->getNext()); //刪除c $edit->show(); } } Client::main();
(七)代理模式(Proxy)
- 定義
代理模式(Proxy)為其他物件提供一種代理以控制對這個物件的訪問。使用代理模式建立代理物件,讓代理物件控制目標物件的訪問(目標物件可以是遠端的物件、建立開銷大的物件或需要安全控制的物件),並且可以在不改變目標物件的情況下新增一些額外的功能.
- 解釋例: 在某些情況下,一個客戶不想或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用,並且可以通過代理物件去掉客戶不能看到的內容和服務或者新增客戶需要的額外服務。
- 例項: 代理主題角色.
-
例項程式碼實現
abstract class Subject { // 抽象主題角色 abstract public function action(); } class RealSubject extends Subject { // 真實主題角色 public function __construct() {} public function action() {} } class ProxySubject extends Subject { // 代理主題角色 private $_real_subject = NULL; public function __construct() {} public function action() { $this->_beforeAction(); if (is_null($this->_real_subject)) { $this->_real_subject = new RealSubject(); } $this->_real_subject->action(); $this->_afterAction(); } private function _beforeAction() { echo '在action前,我想幹點啥....'; } private function _afterAction() { echo '在action後,我還想幹點啥....'; } } // client $subject = new ProxySubject(); $subject->action();//輸出:在action前,我想幹點啥....在action後,我還想幹點啥....
(七)觀察者模式(Observer)
- 定義
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態上發生變化時,會通知所有觀察者物件,使他們能夠自動更新自己。
- 解釋例: 想知道我們們公司最新MM情報嗎?加入公司的MM情報郵件組就行了,tom負責蒐集情報,他發現的新情報不用一個一個通知我們,直接釋出給郵件組,我們作為訂閱者(觀察者)就可以及時收到情報啦
- 例項: 通過php內建的Spl標準類實現觀察者.其中觀察者(obServer)由三個元素組成:SplSubject,SplObjectStrorage,SplObserver,SplSubject具體操作者,SplObjectStorage用於處理儲存物件和刪除物件,SplObserver接收通知的觀察者.本例實現一個購票系統,登入成功之後依次記錄日誌和傳送折扣劵通知.
- 例項程式碼實現
class buyTicket implements SplSubject { private $ticket; private $storage; function __construct(){ $this->storage = new SplObjectStorage(); } function attach( SplObserver $object ){ $this->storage->attach($object); } function detach( SplObserver $object ){ $this->storage->detach($object); } function notify(){ foreach ( $this->storage as $obs ){ $obs->update( $this ); } } //買票成功,傳送通知記錄日誌,送折扣劵 function buyTicket(){ $this->ticket = 'NO.12, $100.'; $this->notify(); } function getTicket(){ return $this->ticket; } } abstract class ticketObject implements SplObserver { /** * 接收通知的觀察者 * 判斷通知來源, 並且相應觀察者執行相應的操作 */ private $buyTicket; function __construct( buyTicket $buyTicket ){ $this->buyTicket = $buyTicket; $buyTicket->attach( $this ); } function update( SplSubject $subject ) { if( $subject === $this->buyTicket ){ $this->doUpdate( $subject ); } } abstract function doUpdate( SplSubject $buyTicket ); } //日誌觀察者接到通知記錄日誌 class ticketLogger extends ticketObject { function doUpdate( SplSubject $buyTicket ){ print '日誌計入 買票 '.$buyTicket->getTicket()."\n"; } } //折扣劵觀察者 接到通知傳送折扣劵 class ticketDid extends ticketObject { function doUpdate( SplSubject $buyTicket ){ print "送10元折扣卷一張".$buyTicket->getTicket(); } } $ticket = new buyTicket(); new ticketLogger( $ticket ); new ticketDid( $ticket ); $ticket->buyTicket();
(八)狀態模式(State)
- 定義
準備一個抽象類,將部分邏輯以具體方法以及具體構造子的形式實現,然後宣告一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現。先制定一個頂級邏輯框架,而將邏輯的細節留給具體的子類去實現。
- 使用場景: 1)如果專案中存在太多的 if {...} elseif {...} else {...} 。那麼你應該考慮狀態模式 2) 如果每個狀態中處理的業務邏輯特別複雜 3)如果程式碼中的狀態相對固定。比如一個電商中購買商品的流程:未支付、已過期、待發貨(已支付)、已發貨、已收貨。那麼這種狀態基本上定下來不會有太大變化,狀態發生在內部中,順序固定,不需要客戶端進行處理。
- 例項: 模擬一個條件判斷的流程.
-
例項程式碼實現
interface State { // 注意這裡的Context 我在後面會講到。不等同於上面的 Context 類哦 public function handle(Context $context); } // 狀態A class StateA implements State { public function handle(Context $context) { if ($context->term == 1) { // 處理邏輯,並終止程式 echo "11111"; } else { $context->setState(new StateB()); $context->request(); } } } // 狀態B class StateB implements State { public function handle(Context $context) { if ($context->term == 2) { // 處理邏輯,並終止程式 echo "2222"; } else { $context->setState(new StateC()); $context->request(); } } } // 狀態C class StateC implements State { public function handle(Context $context) { // 如果還有其他狀態,則繼續往下走。如果沒有,就在次終止程式 if ($context->term == 3) { // 處理邏輯,並終止程式 echo "3333"; } else { echo "4444"; } } } //狀態管理器(上下文環境) class Context { private $state;// 用來儲存 State 物件 public $term; public function setState(State $state) { $this->state = $state; } public function request() { $this->state->handle($this); } } $context = new Context; $context->term = 2; $context->setState(new StateA()); $context->request();
(九)策略模式(Strategy)
- 定義
策略模式針對一組演算法,將每一個演算法封裝到具有共同介面的獨立的類中,從而使得它們可以相互替換。策略模式使得演算法可以在不影響到客戶端的情況下發生變化。策略模式把行為和環境分開。環境類負責維持和查詢行為類,各種演算法在具體的策略類中提供。由於演算法和環境獨立開來,演算法的增減,修改都不會影響到環境和客戶端。
- 解釋例: 跟不同型別的MM約會,要用不同的策略,有的請電影比較好,有的則去吃小吃效果不錯,有的去海邊浪漫最合適,單目的都是為了得到MM的芳心,我的追MM錦囊中有好多Strategy哦。
- 例項: 對二維陣列分別進行id和date排序.
-
例項程式碼實現
class Context { private $comparator; public function __construct(ComparatorInterface $comparator) { $this->comparator = $comparator; } public function executeStrategy(array $elements) : array { uasort($elements, [$this->comparator, 'compare']); return $elements; } } interface ComparatorInterface { public function compare($a, $b): int; } class DateComparator implements ComparatorInterface { public function compare($a, $b): int { $aDate = new \DateTime($a['date']); $bDate = new \DateTime($b['date']); return $aDate <=> $bDate; } } class IdComparator implements ComparatorInterface { public function compare($a, $b): int { return $a['id'] <=> $b['id']; } } $collection = array( 0 => array( 'id' => 4, 'date' => '2019/9/5 15:8:15' ), 1 => array( 'id' => 8, 'date' => '2019/9/5 12:8:15' ), 2 => array( 'id' => -1, 'date' => '2019/9/5 10:8:15' ), 3 => array( 'id' => -9, 'date' => '2019/9/5 11:8:15' ), ); $obj = new Context(new IdComparator()); $elements = $obj->executeStrategy($collection); $firstElement = array_shift($elements); $obj = new Context(new DateComparator()); $elements = $obj->executeStrategy($collection); $firstElement = array_shift($elements); // var_dump($firstElement);
(十)模板方法模式(Template Method)
- 定義
定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。 抽象模板-具體模板
- 例項: 一個旅行模板.
-
例項程式碼實現
abstract class Journey { private $thingsToDo = []; /** * 這是當前類及其子類提供的公共服務 * 注意,它「凍結」了全域性的演算法行為 * 如果你想重寫這個契約,只需要實現一個包含 takeATrip() 方法的介面 */ final public function takeATrip() { $this->thingsToDo[] = $this->enjoyVacation(); $buyGift = $this->buyGift(); if ($buyGift !== null) { $this->thingsToDo[] = $buyGift; } } /** * 這個方法必須要實現,它是這個模式的關鍵點 */ abstract protected function enjoyVacation(): string; /** * 這個方法是可選的,也可能作為演算法的一部分 * 如果需要的話你可以重寫它 * * @return null|string */ protected function buyGift() { return null; } /** * @return string[] */ public function getThingsToDo(): array { return $this->thingsToDo; } } class BeachJourney extends Journey { protected function enjoyVacation(): string { return "Swimming and sun-bathing"; } } class CityJourney extends Journey { protected function enjoyVacation(): string { return "Eat, drink, take photos and sleep"; } protected function buyGift(): string { return "Buy a gift"; } } $beachJourney = new BeachJourney(); $beachJourney->takeATrip(); $beachJourney->getThingsToDo(); $cityJourney = new CityJourney(); $cityJourney->takeATrip(); $cityJourney->getThingsToDo();
(十一)訪問者模式(Visitor)
- 定義
訪問者模式的目的是封裝一些施加於某種資料結構元素之上的操作。一旦這些操作需要修改的話,接受這個操作的資料結構可以保持不變。訪問者模式適用於資料結構相對未定的系統,它把資料結構和作用於結構上的操作之間的耦合解脫開,使得操作集合可以相對自由的演化。訪問者模式使得增加新的操作變的很容易,就是增加一個新的訪問者類。訪問者模式將有關的行為集中到一個訪問者物件中,而不是分散到一個個的節點類中。當使用訪問者模式時,要將盡可能多的物件瀏覽邏輯放在訪問者類中,而不是放到它的子類中。訪問者模式可以跨過幾個類的等級結構訪問屬於不同的等級結構的成員類。
- 什麼是訪問者模式以及特點: 比如我有一個賬單,賬單有收入,支出兩個固定方法。但是訪問賬單的人不確定,有可能是一個或者多個.頻繁的更改資料,但結構不變.比如:雖然每一天賬單的資料都會變化.但是隻有兩類資料,就是支出和收入(結構不變)
- 例項: 賬單有支出和收入兩種方法.BOSS和cpa兩種角色檢視賬單顯示不同的資料.
-
例項程式碼實現
//建立一個賬單介面,有接收訪問者的功能 interface Bill { function accept(AccountBookView $viewer); } //消費單子 class ConsumerBill implements Bill { private $item; private $amount; public function __construct(String $item, int $amount) { $this->item = $item; $this->amount = $amount; } public function accept(AccountBookView $viewer) { $viewer->consumer($this); } public function getItem() { return $this->item; } public function getAmount() { return $this->amount; } } //收入單子 class IncomeBill implements Bill { private $item; private $amount; public function __construct(String $item, int $amount) { $this->item = $item; $this->amount = $amount; } public function accept(AccountBookView $viewer) { $viewer->income($this); } public function getItem() { return $this->item; } public function getAmount() { return $this->amount; } } //訪問者介面 interface AccountBookView { // 檢視消費的單子 function consumer(ConsumerBill $consumerBill); // 檢視收入單子 function income(IncomeBill $incomeBill); } // 老闆類:訪問者是老闆,主要檢視總支出和總收入 class Boss implements AccountBookView { private $totalConsumer; private $totalIncome; // 檢視消費的單子 public function consumer(ConsumerBill $consumerBill) { $this->totalConsumer = $this->totalConsumer + $consumerBill->getAmount(); } // 檢視收入單子 public function income(IncomeBill $incomeBill) { $this->totalIncome = $this->totalIncome + $incomeBill->getAmount(); } public function getTotalConsumer() { echo ("老闆一共消費了".$this->totalConsumer); } public function getTotalIncome() { echo ("老闆一共收入了".$this->totalIncome); } } //會計類:訪問者是會計,主要記錄每筆單子 class CPA implements AccountBookView { private $count = 0; // 檢視消費的單子 public function consumer(ConsumerBill $consumerBill) { $this->count++; if ($consumerBill->getItem() == "消費") { echo ("第" . $this->count . "個單子消費了:" . $consumerBill->getAmount()); } } // 檢視收入單子 public function income(IncomeBill $incomeBill) { if ($incomeBill->getItem() == "收入") { echo ("第" . $this->count . "個單子收入了:" . $incomeBill->getAmount()); } } } //賬單類:用於新增賬單,和為每一個賬單新增訪問者 class AccountBook { private $listBill; // 新增單子 public function add(Bill $bill) { $this->listBill[] = $bill; } // 為每個賬單新增訪問者 public function show(AccountBookView $viewer) { foreach ($this->listBill as $value) { $value->accept($viewer); } } } //測試類 class Test { public static function main() { // 建立消費和收入單子 $consumerBill = new ConsumerBill("消費", 3000); $incomeBill = new IncomeBill("收入", 5000); $consumerBill2 = new ConsumerBill("消費", 4000); $incomeBill2 = new IncomeBill("收入", 8000); // 新增單子 $accountBook = new AccountBook(); $accountBook->add($consumerBill); $accountBook->add($incomeBill); $accountBook->add($consumerBill2); $accountBook->add($incomeBill2); // 建立訪問者 $boss = new Boss(); $cpa = new CPA(); //boss訪問 $accountBook->show($boss); // boss檢視總收入和總消費 $boss->getTotalConsumer(); //7000 $boss->getTotalIncome(); //13000 //cpa檢視消費和收入明細. $accountBook->show($cpa); } } Test::main();
(十二)空物件模式(Null Object)
- 定義
用一個空物件取代 NULL,減少對例項的檢查。這樣的空物件可以在資料不可用的時候提供預設的行為解決在需要一個物件時返回一個null值,使其呼叫函式出錯的情況
- 例項: 1)通過抽象類約束的實現空物件模式 2) 使用魔術方法__call實現空物件模式.
-
例項程式碼實現
//通過抽象約束的空物件 abstract class AbstractCustomer { protected $name; public abstract function isNil():bool; public abstract function getName() : string; } class RealCustomer extends AbstractCustomer { public function __construct(string $name) { $this->name = $name; } public function isNil():bool { return false; } public function getName() : string { return $this->name; } } class NullCustomer extends AbstractCustomer { public function getName() : string { return "Not Available in Customer Database"; } public function isNil():bool { return true; } } class CustomerFactory { public static $users = []; public static function getCustomer($name) { if (in_array($name, self::$users)){ return new RealCustomer($name); } return new NullCustomer(); } } CustomerFactory::$users = ["Rob", "Joe"]; $customer1 = CustomerFactory::getCustomer('Rob'); $customer3 = CustomerFactory::getCustomer('jack'); echo $customer3->getName(); //使用魔術方法__call生成空物件 class Person{ public function code(){ echo 'code makes me happy'.PHP_EOL; } } class NullObject{ public function __call($method,$arg){ echo 'this is NullObject'; } } //定義一個生成物件函式,只有PHPer才允許生成物件 function getPerson($name){ if($name=='PHPer'){ return new Person; }else{ return new NullObject; } } $phper = getPerson('phper'); $phper->code();
(十三)規格模式(Specification)
- 定義
構建一個清晰的業務規則規範,其中每條規則都能被針對性地檢查。每個規範類中都有一個稱為 isSatisfiedBy 的方法,方法判斷給定的規則是否滿足規範從而返回 true 或 false。
- 例項: 用 與,或,非 三種方式檢驗價格是否達標
-
例項程式碼實現
class Item { /** * @var float */ private $price; public function __construct(float $price) { $this->price = $price; } public function getPrice(): float { return $this->price; } } interface SpecificationInterface { public function isSatisfiedBy(Item $item): bool; } class OrSpecification implements SpecificationInterface { /** * @var SpecificationInterface[] */ private $specifications; /** * @param SpecificationInterface[] ...$specifications */ public function __construct(SpecificationInterface ...$specifications) { $this->specifications = $specifications; } /** * 如果有一條規則符合條件,返回 true,否則返回 false */ public function isSatisfiedBy(Item $item): bool { foreach ($this->specifications as $specification) { if ($specification->isSatisfiedBy($item)) { return true; } } return false; } } class PriceSpecification implements SpecificationInterface { /** * @var float|null */ private $maxPrice; /** * @var float|null */ private $minPrice; /** * @param float $minPrice * @param float $maxPrice */ public function __construct($minPrice, $maxPrice) { $this->minPrice = $minPrice; $this->maxPrice = $maxPrice; } public function isSatisfiedBy(Item $item): bool { if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) { return false; } if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) { return false; } return true; } } class AndSpecification implements SpecificationInterface { /** * @var SpecificationInterface[] */ private $specifications; /** * @param SpecificationInterface[] ...$specifications */ public function __construct(SpecificationInterface ...$specifications) { $this->specifications = $specifications; } /** * 如果有一條規則不符合條件,返回 false,否則返回 true */ public function isSatisfiedBy(Item $item): bool { foreach ($this->specifications as $specification) { if (!$specification->isSatisfiedBy($item)) { return false; } } return true; } } class NotSpecification implements SpecificationInterface { /** * @var SpecificationInterface */ private $specification; public function __construct(SpecificationInterface $specification) { $this->specification = $specification; } public function isSatisfiedBy(Item $item): bool { return !$this->specification->isSatisfiedBy($item); } } $spec1 = new PriceSpecification(50, 100); $spec2 = new PriceSpecification(50, 200); $orSpec = new OrSpecification($spec1, $spec2); echo $orSpec->isSatisfiedBy(new Item(60)); $andSpec = new AndSpecification($spec1, $spec2); echo $andSpec->isSatisfiedBy(new Item(60)); $notSpec = new NotSpecification($spec1); echo $notSpec->isSatisfiedBy(new Item(60));