裝飾器模式允許向一個現(xiàn)有的對象添加新的功能,同時又不改變其結構。本篇文章帶大家了解PHP中的裝飾器模式,介紹一下裝飾器的好處以及最適用于的場景。
工廠模式告一段落,我們來研究其他一些模式。不知道各位大佬有沒有嘗試過女裝?據(jù)說女裝大佬程序員很多喲。其實,今天的裝飾器模式就和化妝這件事很像。相信如果有程序媛MM在的話,馬上就能和你講清楚這個設計模式。
Gof類圖及解釋
裝飾這兩個字,我們暫且把他變成化妝。首先你得有一張臉,然后打底,然后上妝,可以早上來個淡妝上班,也可以下班的時候補成濃妝出去嗨。當然,碼農(nóng)們下班的時間點正好是能趕上夜場的下半場的。話說回來,不管怎么化妝,你的臉還是你的臉,有可能可以化成別人不認識的另一個人,但這的的確確還是你的臉。這就是裝飾器,對對象(臉)進行各種裝飾(化妝),讓這個臉更好看(增加職責)。
GoF定義:動態(tài)地給一個對象添加一些額外的職責,就增加功能來說,Decorator模式相比生成子類更為靈活
GoF類圖:
代碼實現(xiàn):
interface Component{ public function operation(); } class ConcreteComponent implements Component{ public function operation(){ echo "I'm face!" . PHP_EOL; } }
很簡單的一個接口和一個實現(xiàn),這里我們就把具體的實現(xiàn)類看作是一張臉吧!
abstract class Decorator implements Component{ protected $component; public function __construct(Component $component){ $this->component = $component; } }
抽象的裝飾者類,實現(xiàn)Component接口,但并不實現(xiàn)operation()方法,讓子類去實現(xiàn)。在這里主要保存一個Componet的引用,一會就要對他進行裝飾。對應到上方的具體類,我們就是要準備給臉化妝啦!
class ConcreteDecoratorA extends Decorator{ public $addedState = 1; // 沒什么實際意義的屬性,只是區(qū)別于ConcreteDecoratorB public function operation(){ echo $this->component->operation() . "Push " . $this->addedState . " cream!" . PHP_EOL; } } class ConcreteDecoratorB extends Decorator{ public function operation(){ $this->component->operation(); $this->addedBehavior(); } // 沒什么實際意義的方法,只是區(qū)別于ConcreteDecoratorA public function addedBehavior(){ echo "Push 2 cream!" . PHP_EOL; } }
兩個具體裝飾者。在這里我是涂了兩次霜,畢竟是純爺們,對化妝這事兒真的是不了解。好像第一步應該先是打粉底吧?不過這次就這樣,我們這兩個裝飾器實現(xiàn)的就是給臉上涂兩層霜。
- 從代碼中可以看出,我們是一直對具體的那個ConcreteComponent對象來進行包裝
- 再往下的話其實我們是對他的operation()這個方法包裝了兩次,每次都是在前一次的基礎上加了一點點東西
- 不要糾結于A和B裝飾器上的added屬性和方法,他們只是GoF類圖中用以區(qū)別這兩個裝飾器不是同一個東西,每個裝飾器都可以干很多別的事,Component對象也不一定只有operation()這一個方法,我們可以選擇性的去裝飾對象中的全部或者部分方法
- 好像我們都繼承了Component,直接子類一路重寫不就行了,搞這費勁干嘛?親,了解下組合的概念喲,我們的Decorator父類里面是一個真實對象的引用哦,解耦了自身哦,我們只給真實的對象去做包裝,您可別直接實例化裝飾器來直接用
- 還是沒懂?好處呢?老系統(tǒng)的類啊、方法啊你敢隨便亂改?想給前任寫的牛(S)逼(B)代碼擴展新功能時不妨試試裝飾器這貨,說不定有奇效!
手機這玩意干不過某米、某O、某為,這沒法玩呀,好吧,哥們?nèi)P淖鍪謾C殼吧!嗯,我先準備了一個透明殼(Component),貌似有點丑,沒辦法,誰叫哥們窮。給某米的加上各種純色(DecoratorA1),然后背后印上各種顏色的植物(DecoratorB1)吧;某O的手機最近喜歡找流量明顯做代言,那我給他的手機殼就用各種炫彩色(DecoratorA2)和明星的卡通頭像(DecoratorB2);最后的某為,好像手機已經(jīng)開始引領業(yè)界潮流了,折疊屏這玩意不是要砸我這賣手機殼的生意嘛!!好吧,哥不給你們做了,還是跟我的某米、某O混去吧!!
完整代碼:裝飾器模式
https://github.com/zhangyue0503/designpatterns-php/blob/master/04.decorator/source/decorator.php
實例
繼續(xù)來發(fā)短信,之前我們用工廠模式解決了多個短信運營商的問題。這回我們要解決的是短信內(nèi)容模板的問題。對于推廣類的短信來說,根據(jù)最新的廣告法,我們是不能出現(xiàn)“全國第一”、“全世界第一”這類的詞語的,當然,一些不太文明的用語我們也是不能使用的。
現(xiàn)在的情況是這樣的,我們有一個很早之前的短信模板類,里面的內(nèi)容是固定的,老系統(tǒng)依然還是使用這個模板,老系統(tǒng)是面對的內(nèi)部員工,對語言內(nèi)容的要求不高。而新系統(tǒng)則需要向全網(wǎng)發(fā)送,也就是內(nèi)外部的用戶都要發(fā)送。這時,我們可以用裝飾器模式來對老系統(tǒng)的短信模板進行包裝。其實說簡單點,我們就是用裝飾器來做文本替換的功能。好處呢?當然是可以不去改動原來的模板類中的方法就實現(xiàn)了對老模板內(nèi)容的修改擴展等。
短信發(fā)送類圖:
完整源碼:短信發(fā)送裝飾器方法
https://github.com/zhangyue0503/designpatterns-php/blob/master/04.decorator/source/message-decorator.php
<?php // 短信模板接口 interface MessageTemplate { public function message(); } // 假設有很多模板實現(xiàn)了上面的短信模板接口 // 下面這個是其中一個優(yōu)惠券發(fā)送的模板實現(xiàn) class CouponMessageTemplate implements MessageTemplate { public function message() { return '優(yōu)惠券信息:我們是全國第一的牛X產(chǎn)品哦,送您十張優(yōu)惠券!'; } } // 我們來準備好裝飾上面那個過時的短信模板 abstract class DecoratorMessageTemplate implements MessageTemplate { public $template; public function __construct($template) { $this->template = $template; } } // 過濾新廣告法中不允許出現(xiàn)的詞匯 class AdFilterDecoratorMessage extends DecoratorMessageTemplate { public function message() { return str_replace('全國第一', '全國第二', $this->template->message()); } } // 使用我們的大數(shù)據(jù)部門同事自動生成的新詞庫來過濾敏感詞匯,這塊過濾不是強制要過濾的內(nèi)容,可選擇使用 class SensitiveFilterDecoratorMessage extends DecoratorMessageTemplate { public $bigDataFilterWords = ['牛X']; public $bigDataReplaceWords = ['好用']; public function message() { return str_replace($this->bigDataFilterWords, $this->bigDataReplaceWords, $this->template->message()); } } // 客戶端,發(fā)送接口,需要使用模板來進行短信發(fā)送 class Message { public $msgType = 'old'; public function send(MessageTemplate $mt) { // 發(fā)送出去咯 if ($this->msgType == 'old') { echo '面向內(nèi)網(wǎng)用戶發(fā)送' . $mt->message() . PHP_EOL; } else if ($this->msgType == 'new') { echo '面向全網(wǎng)用戶發(fā)送' . $mt->message() . PHP_EOL; } } } $template = new CouponMessageTemplate(); $message = new Message(); // 老系統(tǒng),用不著過濾,只有內(nèi)部用戶才看得到 $message->send($template); // 新系統(tǒng),面向全網(wǎng)發(fā)布的,需要過濾一下內(nèi)容哦 $message->msgType = 'new'; $template = new AdFilterDecoratorMessage($template); $template = new SensitiveFilterDecoratorMessage($template); // 過濾完了,發(fā)送吧 $message->send($template);
說明
- 裝飾器的最大好處:一是不改變原有代碼的情況下對原有代碼中的內(nèi)容進行擴展,開放封閉原則;二是每個裝飾器完成自己的功能,單一職責;三是用組合實現(xiàn)了繼承的感覺;
- 最適用于:給老系統(tǒng)進行擴展
- 要小心:過多的裝飾者會把你搞暈的
- 不一定都是對同一個方法進行裝飾,其實裝飾者應該