php設計模式(4)-- 裝飾器模式

我的設計模式系列文章
[url=http://xieye.iteye.com/blog/2404077]php設計模式(1)-- 觀察者模式 -- spl標準寫法[/url]
[url=http://xieye.iteye.com/blog/2404082]php設計模式(2)-- 觀察者模式 -- 用trait來改進的寫法[/url]
[url=http://xieye.iteye.com/blog/2404140]php設計模式(3)-- 責任鏈(職責鏈)模式[/url]
[url=http://xieye.iteye.com/blog/2404169]php設計模式(4)-- 裝飾器模式[/url]

[size=x-large]分析[/size]

網上的套話就不說了。

圖片來自紅黑聯盟:
[img]http://dl2.iteye.com/upload/attachment/0128/0856/d68133bf-cdb2-31b7-ba92-a37a09ac48ff.jpg[/img]

上圖中,Componet 對應我這裏的 Display
ConcreteComponet 對應我這裏的 BasicDisplay
Decorator 對應我這裏的 Border
剩下兩個分別對應 FullBorder 和 SiderBorder

裝飾器適用場合:假設一個對象有某種功能,在有些時候,需要對這功能增強,但有些時候,又需要使用原有功能。這時使用裝飾器。

裝飾器和責任鏈都會有一個長長的對象關聯鏈條,其差異是:責任鏈強調對同一個消息的不同處理,而不是功能增強。[b]責任鏈是多個功能[/b],而[b]裝飾器是一個功能,但是這個功能有時需要一些小改變,改來改去是同一個功能[/b]。

java的流使用了裝飾器,說明裝飾器也可以改變結果的表現形式,總之,功能增強是個泛泛而言的事情,能做到事情很多。

[b]注意:和觀察者,以及責任鏈一樣,類與類的具體哪個和哪個關聯,先後順序等,都放在客戶端代碼裏![/b]

現在我們構造需求。

假設,我們有一個文檔,需要打印機打印出來,但是我們希望打印一些邊框,讓文檔漂亮些,邊框跟文檔內容沒關係。但是同一個功能:打印。所以我們使用裝飾器。

[size=x-large]代碼實現[/size]


<?php
/**
* 裝飾器模式學習代碼。
*
* 位於最頂層,表示了整個設計模式示例的功能:打印字符串
*
* 這個程序也可以包裝多行的文本,只是代碼改得複雜一些,不利於看清設計模式。
*/
abstract class Display
{
public abstract function getColumns(); //取得橫向的字數,把責任委託給子類,所以抽象,下同
//觀察子類可知,只要有一個類使用到了,
//需要所有的類都要有這個方法!

public abstract function getRows(); //取得縱向的行數,把責任委託給子類
public abstract function getRowText($row);//取得第row行的字符串

public function show() { //因爲這個方法的實現是固定的,所以寫這裏
for ($i = 0; $i < $this->getRows(); $i++) {
echo $this->getRowText($i) . PHP_EOL;
}
echo PHP_EOL;
}
}

/**
* 注意此類一定被包裹在覈心,和別的類不同,雖然都是繼承Display類
* 所以我取名basic
*/
class BasicDisplay extends Display
{
private $string; //最裏面的,一定會被打印出的字符串

public function __construct($string) { //需要在外部指定
$this->string = $string;
}

public function getColumns() { //注意!,僅被某類調用,卻寫到每個類中!!
return strlen($this->string);
}

public function getRows() { //核心只打印一行
return 1;
}

public function getRowText($row) { //僅在row爲0時才返回
if ($row == 0) {
return $this->string;
} else {
return null;
}
}

}


/**
* 因爲外框又有多種,所以把共性抽取出來,形成此抽象類,其中,
* 還確定了每個裝飾器子類都有的構造方法和屬性,通常就是屬於共同接口的對象
*/
abstract class Border extends Display
{
protected $display; //注意到:是同一接口的對象,php
//不像java能表達出類型,但實際是的

protected function __construct(Display $display) { //後面可看到,子類實際可以擴展構造方法
$this->display = $display;
}
}

/**
* 在字符兩邊輸出特定字符(由程序外部指定)的外框類,
* 通過Border間接繼承Display
*
*/
class SideBorder extends Border
{
//裝飾用的字符,會寫到兩邊
private $borderChar;

public function __construct(Display $display, $ch) {//注意重寫了構造方法。
parent::__construct($display);
$this->borderChar = $ch;
}

public function getColumns() {// 左右各加一個字符,所以寬度加2
return 1+ $this->display->getColumns() + 1;
}

public function getRows() {
return $this->display->getRows();
}

/**
* 最後的顯示效果如 |hello, world|
* 其中兩邊的|只是示例,由外部傳入的。
* 根據php的類型,沒有字符類,所以請確保只傳入一個字符。這裏沒有判斷,也可以拋異常等。
*/
public function getRowText($row) { // 注意這其實在一個循環裏,只是每行做同樣的處理罷了。
return $this->borderChar . $this->display->getRowText($row) . $this->borderChar;
}
}

/**
* 把字符包裹於其中的外框類
* 通過Border間接繼承Display
*
*/
class FullBorder extends Border
{
private $borderChar;

public function __construct(Display $display) {
parent::__construct($display);
}

//這些方法很重要,保證了上下的字符對齊(假定字符寬度相等)
//注意到:雖然別的類的該方法似乎沒有用到,
//實際在這裏用到了,讓本類可以知道里面內核的字符寬度
public function getColumns() {
return 1 + $this->display->getColumns() + 1;
}

public function getRows() {
return 1 + $this->display->getRows() + 1;
}

/**
* 把行數確定爲核心內容加2後,見上getRows,就可以在頂部和底部輸出裝飾
* +-------------------+
* +-------------------+
*
* 然後,在內容的兩邊輸出 | 字符
*/
public function getRowText($row) {
if ($row == 0) { // 第1行
return '+' . $this->makeLine('-', $this->display->getColumns()) . '+';
} elseif ($row == $this->display->getRows() + 1) { // 最後一行,= 原有總行數 + 1,因爲行數從1算,row從0算。
return '+' . $this->makeLine('-', $this->display->getColumns()) . '+';
} else {
return '|' . $this->display->getRowText($row - 1) . '|';//-1 是因爲有一個錯位,多了一行
}
}

private function makeLine($ch, $count) {
$s = '';
for ($i = 0; $i < $count; $i++) {
$s .= $ch;
}
return $s;
}

}

//打印“Hello,world”,沒有任何裝飾
$b1 = new BasicDisplay('Hello, world.');
$b1->show();

//把裝飾字符'#'加在b1的左右兩邊
$b2 = new SideBorder($b1, '#');
$b2->show();

//把b2加上裝飾外框
$b3 = new FullBorder($b2);
$b3->show();

//b4在覈心的外面加上了多重外框,請仔細觀察圖形與每個裝飾器的對應關係,很有意思的。
$b4 = new SideBorder(
new FullBorder(
new FullBorder(
new SideBorder(
new FullBorder(
new BasicDisplay('Hello, world.')
), '*'
)
)
), '/'
);
$b4->show();


[size=x-large]結果展示,非常精巧[/size]

Hello, world.

#Hello, world.#

+---------------+
|#Hello, world.#|
+---------------+

/+-------------------+/
/|+-----------------+|/
/||*+-------------+*||/
/||*|Hello, world.|*||/
/||*+-------------+*||/
/|+-----------------+|/
/+-------------------+/
發佈了312 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章