續上一篇:PHP行為型設計模式(一),介紹第二類行為型設計模式。
兩個類之間:
- 觀察者模式 (observer Pattern)
- 迭代器模式 (Iterator Pattern)
- 責任鏈模式 (Chain of Responsibility Pattern)
- 命令模式 (Command Pattern)
PHP設計模式(十四)—觀察者模式 (observer Pattern)
觀察者模式 (observer Pattern): 定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。也叫釋出-訂閱模式
(一)為什麼需要觀察者模式
1,一個物件狀態改變給其他物件通知的問題,而且要考慮到易用和低耦合,保證高度的協作
2,完美的將觀察者和被觀察的物件分離開,使得每個類將重點放在某一個功能上,一個物件只做一件事情。
3,觀察者模式在模組之間劃定了清晰的界限,提高了應用程式的可維護性和重用性。
(二)觀察者模式 UML圖
(三)簡單例項
觀察者模式也叫釋出訂閱模式,如果說我們現在在做一個系統。我們讓所有客戶端訂閱我們的服務端,那麼當我們的服務端有更新資訊的時候,就通知客戶端去更新。這裡的服務端就是被觀察者,客戶端就是觀察者。
<?php
//抽象被觀察者
abstract class Subject{
//定義一個觀察者陣列
private $observers = array();
//增加觀察者方法
public function addObserver(Observer $observer){
$this->observers[] = $observer;
echo "新增觀察者成功".PHP_EOL;
}
//刪除觀察者方法
public function delObserver(Observer $observer){
$key = array_search($observer,$this->observers); //判斷是否有該觀察者存在
if($observer===$this->observers[$key]) { //值雖然相同 但有可能不是同一個物件 ,所以使用全等判斷
unset($this->observers[$key]);
echo '刪除觀察者成功'.PHP_EOL;
} else{
echo '觀察者不存在,無需刪除'.PHP_EOL;
}
}
//通知所有觀察者
public function notifyObservers(){
foreach($this->observers as $observer){
$observer->update();
}
}
}
//具體被觀察者 服務端
class Server extends Subject{
//具體被觀察者業務 釋出一條資訊,並通知所有客戶端
public function publish(){
echo '今天天氣很好,我釋出了更新包'.PHP_EOL;
$this->notifyObservers();
}
}
//抽象觀察者介面
Interface Observer{
public function update();
}
//具體觀察者類
//微信端
class Wechat implements Observer{
public function update(){
echo '通知已接收,微信更新完畢'.PHP_EOL;
}
}
//web端
class Web implements Observer{
public function update(){
echo '通知已接收,web端系統更新中'.PHP_EOL;
}
}
//app端
class App implements Observer{
public function update(){
echo '通知已接收,APP端稍後更新'.PHP_EOL;
}
}
//例項化被觀察者
$server = new Server ;
//例項化觀察者
$wechat = new Wechat ;
$web = new Web ;
$app = new App;
//新增被觀察者
$server->addObserver($wechat);
$server->addObserver($web);
$server->addObserver($app);
//被觀察者 釋出資訊
$server->publish();
//刪除觀察者
$server->delObserver($wechat);
//再次釋出資訊
$server->publish();
//嘗試刪除一個未新增成觀察者的物件
$server->delObserver(new Web);
//再次釋出資訊
$server->publish();複製程式碼
觀察者模式的一個關鍵詞就是觸發,被觀察者的動作觸發觀察者的做出對應的響應。觀察者模式的另一個常用領域在於外掛系統。
在PHP中觀察者的另一種實現方式,是通過實現SplSubject介面和SplObserver,這種實現方法涉及到spl(standard php library)的內容,我將會在SPL相關文章中進行介紹。歡迎大家到時候閱讀指正
PHP設計模式(十五)—迭代器模式(Iterator Pattern)
迭代器模式(Iterator Pattern):迭代器模式可幫組構造特定的物件,那些物件能夠提供單一的標準介面迴圈或迭代任何型別的可計數資料。
(一)為什麼需要迭代器模式
1,我們想要向遍歷陣列那樣,遍歷物件,或是遍歷一個容器。
2,迭代器模式可以隱藏遍歷元素所需的操作-
(二)迭代器模式 UML圖
上圖是擷取了《PHP設計模式》中迭代器的UML圖,僅供參考。在PHP中我們對迭代器模式的使用,通常只需實現Iterator
介面即可。
下圖給出實現 Interator
介面 所需實現的方法
(三)簡單例項
現在我們把一個類當做一個容器,讓其實現Iterator
介面,使其可以被遍歷。
<?php
class ArrayContainer implements Iterator
{
protected $data = array();
protected $index ;
public function __construct($data)
{
$this->data = $data;
}
//返回當前指標指向資料
public function current()
{
return $this->data[$this->index];
}
//指標+1
public function next()
{
$this->index ++;
}
//驗證指標是否越界
public function valid()
{
return $this->index < count($this->data);
}
//重置指標
public function rewind()
{
$this->index = 0;
}
//返回當前指標
public function key()
{
return $this->index;
}
}
//初始化陣列容器
$arr = array(0=>'唐朝',1=>'宋朝',2=>'元朝');
$container = new ArrayContainer($arr);
//遍歷陣列容器
foreach($container as $a => $dynasty){
echo '如果有時光機,我想去'.$dynasty.PHP_EOL;
}複製程式碼
迭代器其實也類似於資料庫的遊標,可以在容器內上下翻滾,遍歷它所需要檢視的元素。
通過實現Iterator
介面,我們就可以把一個物件裡的資料當一個陣列一樣遍歷。也許你會說我把一個陣列直接遍歷不就行了嗎,為什麼你還要把陣列扔給容器物件,再遍歷容器物件呢?是這樣的,通過容器物件,我們可以隱藏我們foreach
的操作。比如說,我想遍歷時,一個元素輸出,一個元素不輸出怎麼辦呢?利用迭代器模式,你只需把容器物件中的next
方法中的index++
語句改為index+=2
即可。這點,你可以試試。
為何實現一個Iterator
介面就必須實現current
那些方法呢?其實foreach
容器物件的時候,PHP是自動幫我們依次呼叫了,valid
,next
這些方法。具體的呼叫順序,還有Iterator
介面的實現,其實是屬於SPL(Standard PHP Library)的內容。對於迭代器介面的更多內容,我將會在SPL相關的文章中,進行介紹。歡迎大家到時指正交流。
PHP設計模式(十六)—責任鏈模式(Chain of Responsibility Pattern)
責任鏈模式( Chain of Responsibility Pattern): 為請求建立了一個接收者物件的鏈,並沿這條鏈傳遞該請求,直到有物件處理它為止。這種模式能夠給予請求的型別,對請求的傳送者和接收者進行解耦。
(一)為什麼需要責任鏈模式
1,將請求的傳送者和請求的處理者解耦了。責任鏈上的處理者負責處理請求,客戶只需要將請求傳送到職責鏈上即可,無須關心請求的處理細節和請求的傳遞。
2, 發出這個請求的客戶端並不知道鏈上的哪一個物件最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任
(二)責任鏈模式 UML圖
(三)簡單例項
如果我們現在做一個員工系統,如果說公司有規定說員工有請示,要根據請示的級別由不同人批示。比如請假由組長批示,休假由主管批示,辭職由總經理批示。好了,如果我們使用程式碼如何實現呢?員工類,組長類,主管類,總經理類。那麼員工物件的請示直接交給總經理嗎,顯然是不好。如果我們按照現實世界的邏輯來實現,那應該是怎樣的呢?(我很贊同一句話:任何程式碼的邏輯都在現實世界能找到與之對應的場景,如果沒有,就是你的邏輯有問題
)
<?php
//抽象處理者
abstract class Handler{
private $next_handler ;//存放下一處理物件
private $lever = 0; //處理級別預設0
abstract protected function response(); //處理者回應
//設定處理級別
public function setHandlerLevel($lever){
$this->lever = $lever ;
}
//設定下一個處理者是誰
public function setNext(Handler $handler){
$this->next_handler = $handler;
$this->next_handler->setHandlerLevel($this->lever+1);
}
//責任鏈實現主要方法,判斷責任是否屬於該物件,不屬於則轉發給下一級。使用final不允許重寫
final public function handlerMessage(Request $request){
if($this->lever == $request->getLever()){
$this->response();
}else{
if($this->next_handler != null){
$this->next_handler->handlerMessage($request);
}else{
echo '洗洗睡吧,沒人理你'.PHP_EOL;
}
}
}
}
//具體處理者
// headman 組長
class HeadMan extends Handler{
protected function response(){
echo '組長回覆你:同意你的請求'.PHP_EOL;
}
}
//主管director
class Director extends Handler{
protected function response(){
echo '主管回覆你:知道了,退下'.PHP_EOL;
}
}
//經理manager
class Manager extends Handler{
protected function response(){
echo '經理回覆你:容朕思慮,再議'.PHP_EOL;
}
}
//請求類
class Request{
protected $level = array('請假'=>0,'休假'=>1,'辭職'=>2);//測試方便,預設設定好請示級別對應關係
protected $request;
public function __construct($request){
$this->request = $request;
}
public function getLever(){
//獲取請求對應的級別,如果該請求沒有對應級別 則返回-1
return array_key_exists($this->request,$this->level)?$this->level[$this->request]:-1;
}
}
//例項化處理者
$headman = new HeadMan;
$director = new Director;
$manager = new Manager;
//責任鏈組裝
$headman->setNext($director);
$director->setNext($manager);
//傳遞請求
$headman->handlerMessage(new Request('請假'));
$headman->handlerMessage(new Request('休假'));
$headman->handlerMessage(new Request('辭職'));
$headman->handlerMessage(new Request('加薪'));複製程式碼
具體的處理者即對應UML圖中的ConcreteHandler
,請求類對應UML圖的client
。使用責任鏈,可以使對應的處理者有對應的單一的response
方法。請求只需交給最低階的處理者,由屬性$lever判斷,一層層傳遞到與請求級別相同的處理者中做出對應的回應。請求無需知道,要交給對應的那個處理者。
當然,當責任鏈過長時也會引起效能問題。對此我們應避免使用過長的責任鏈。
PHP設計模式(十七)—命令模式 (Command Pattern)
命令模式 (Command Pattern): 將一個請求封裝為一個物件,從而使我們可用不同的請求對客戶進行引數化;對請求排隊或者記錄請求日誌,以及支援可撤銷的操作。命令模式是一種物件行為型模式,其別名為動作模式(Action Pattern)或事務模式(Transaction Pattern)。
(一)為什麼需要命令模式
1, 使用命令模式,能夠讓請求傳送者與請求接收者消除彼此之間的耦合,讓物件之間的呼叫關係更加靈活。
2,使用命令模式可以比較容易地設計一個命令佇列和巨集命令(組合命令),而且新的命令可以很容易地加入系統
(二)命令模式 UML圖
(三)簡單例項
有關命令模式的一個經典的例子,就是電視機與遙控器。如果按照命令模式的UML圖那麼,就有這樣的對應關係:電視機是請求的接收者,遙控器是請求的傳送者。遙控器上有一些按鈕,不同的按鈕對應電視機的不同操作。這些按鈕就是對應的具體命令類。抽象命令角色由一個命令介面來扮演,有三個具體的命令類實現了抽象命令介面,這三個具體命令類分別代表三種操作:開啟電視機、關閉電視機和切換頻道。
<?php
//抽象命令角色
abstract class Command{
protected $receiver;
function __construct(TV $receiver)
{
$this->receiver = $receiver;
}
abstract public function execute();
}
//具體命令角色 開機命令
class CommandOn extends Command
{
public function execute()
{
$this->receiver->action();
}
}
//具體命令角色 關機機命令
class CommandOff extends Command
{
public function execute()
{
$this->receiver->action();
}
}
//命令傳送者 --遙控器
class Invoker
{
protected $command;
public function setCommand(Command $command)
{
$this->command = $command;
}
public function send()
{
$this->command->execute();
}
}
//命令接收者 Receiver =》 TV
class TV
{
public function action()
{
echo "接收到命令,執行成功".PHP_EOL;
}
}
//例項化命令接收者 -------買一個電視機
$receiver = new TV();
//例項化命令傳送者-------配一個遙控器
$invoker = new Invoker();
//例項化具體命令角色 -------設定遙控器按鍵匹配電視機
$commandOn = new CommandOn($receiver);
$commandOff = new CommandOff($receiver);
//設定命令 ----------按下開機按鈕
$invoker->setCommand($commandOn);
//傳送命令
$invoker->send();
//設定命令 -----------按下關機按鈕
$invoker->setCommand($commandOff);
//傳送命令
$invoker->send();複製程式碼
在實際使用中,命令模式的receiver
經常是一個抽象類,就是對於不同的命令,它都有對應的具體命令接收者。命令模式的本質是對命令進行封裝,將發出命令的責任和執行命令的責任分割開。
上一篇::PHP行為型設計模式(一)
感謝閱讀,由於筆者也是初學設計模式,能力有限,文章不可避免地有失偏頗
後續更新 PHP行為型設計模式(三)介紹,歡迎大家評論指正
我最近的學習總結: