我們在使用面向對象的日常開發過程中,或許會碰見需要對某個方法或者某個對象,添加新的行為。然而常見的做法是,寫一個子類繼承需要改寫的類,然後去重新實現類的方法。
但是裝飾器模式(Decorator),可以動態地添加修改類的功能,在使用裝飾器模式,僅需在運行時添加一個裝飾器對象既可實現,相對與生成子類更加的靈活。
在我們需要改寫一個類的時候通常的做法是采用繼承的方式來重新方法,如下代碼
/* * 比如我們需要改寫一串字符串的樣式,采用繼承的寫法。 */ class Canvas { function draw($width = 20, $height = 10) { for($i = 0; $i < $height; $i++) { for($j = 0; $j < $width; $j++) { echo '*'; } echo '<br/>'; } } } class Canvas2 extends Canvas { function draw($width = 20, $height = 10) { echo "<div style='color: red;'>"; parent::draw($width, $height); echo "</div>"; } } $Canvas2 = new Canvas2(); $Canvas2->draw();
對於上面的這種寫法,假如我們需要多增加一個一種樣式就需要多一個繼承。接下來使用裝飾器模式(Decorator)就會方便很多。
/* * 首先聲明一個裝飾器的接口 */ interface DrawDecorator { function beforeDraw(); function afterDraw(); }
接下來再分別添加兩個裝飾類,來繼承接口,實現接口中的方法
/* * 顏色裝飾 */ class ColorDrawDecorator implements DrawDecorator { protected $color; function __construct($color = 'red') { $this->color = $color; } function beforeDraw() { echo "<div style='color: {$this->color};'>"; } function afterDraw() { echo "</div>"; } } /* * 字體大小裝飾 */ class SizeDrawDecorator implements DrawDecorator { protected $size; function __construct($size = '14px') { $this->size = $size; } function beforeDraw() { echo "<div style='font-size: {$this->size};'>"; } function afterDraw() { echo "</div>"; } }
接下來就是使用我們前面所創建的裝飾類
/* * 創建一個畫布類 */ class Canvas { protected $decorators = array(); //用來存放裝飾的數組 function draw($width = 20, $height = 10) { $this->beforeDraw(); for($i = 0; $i < $height; $i++) { for($j = 0; $j < $width; $j++) { echo '*'; } echo '<br/>'; } $this->afterDraw(); } //添加裝飾器的方法 function addDecorator(DrawDecorator $decorator) { $this->decorators[] = $decorator; } function beforeDraw() { foreach($this->decorators as $decorator) { $decorator->beforeDraw(); } } function afterDraw() { $decorators = array_reverse($this->decorators); foreach($decorators as $decorator) { $decorator->afterDraw(); } } } $Canvas = new Canvas(); $Canvas->addDecorator(new ColorDrawDecorator('red')); $Canvas->addDecorator(new SizeDrawDecorator('9px')); $Canvas->draw(20, 10);