#### 第21章:
#### 設計模式
#### 21.1 面向對象編程(OOP)
##### 21.1.1 為什么采用面向對象編程
##### 解決問題更容易
設計計算機程序是為了解決人類的問題。所有有了將大問題分解為小問題的技術"動態編程"。
我們先嘗試分解一個問題:五月是否可以去西雅圖旅游。
1. 西雅圖是否存在?(yes/no):答案=yes。
2. 西雅圖是否有機場?(yes/no):答案是=yes,機場標識=SEA。
3. 五月有沒有去SEA的航班?(yes/no):答案是=yes。
4. 五月有沒有計劃外的事情?(yes/no):答案=可能有。
5. 五月前是否可以把計劃外的事情做完?(yes/no):答案=yes,可以做完;no,不能做完。
6. 西雅圖的旅游業和商業能保證安全嘛?(yes/no):答案=yes。
7. 從我國有到美國(西雅圖所在國家)的簽證嗎?(yes/no):答案=yes。
8. 美國允許中國人入境嗎?(yes/no)答案=yes。
9. 需要接種疫苗嗎?(yes/no)答案=yes。
以上總體可以將`五月是否可以去西雅圖旅游`看作一個復雜的事情,只是細分為了多個小問題,提供了`yes`和`no`
兩種答案。而這多個小問題還可以繼續細分為更多小問題,比如答案是`可能`的,說明需要詢問更多問題得到一個明確答案。
##### 模塊化
將一個問題細分為多個子問題的過程就是模塊化。
:-: 
? 問題模塊化過程
OOP是為了將復雜的問題簡單化,將問題"分而治之"。越復雜的問題模塊化的意義越大。
##### 類與對象
對某個問題模塊化后需要`組織模塊`,并且需要讓它們相互協作共同處理解決大問題,所以將一個模塊看作是相關處理函數和屬性的集合。我們將這種集合稱為`類`,它擁有`屬性`和`方法`,它的作用是用來專門處理這個模塊。
##### 單一職責
可以把類看作是有共同特征對象的集合。比如"五月是否可以去西雅圖旅游"、"六月是否可以去西雅圖旅游"、"五月是否可以去夏威夷旅游"可以看作有共同特征的由同一模塊化問題抽象出來的類所具體生成的三個解決問題的對象。所以我們得到面了向對象的`首要原則:單一職責,一個類應當只有一個職責`,所有滿足單一職責的類在OOP編程中都可以看作是"高質量"的類。
并不是指一個類不能有多個職責,而是要把一個復雜的問題分解為簡單問題就是為了將它轉化為多個容易解決的問題逐個解決。這樣能夠更容易組織模塊和解決問題的邏輯。
##### 21.1.2 速度
程序員應該關心兩種速度:`代碼在系統中運行的速度`和`創建、更新程序需要花費的時間`。
##### 代碼運行的速度
影響代碼運行速度的因素有:硬件能力(CPU處理能力、內存大小以及讀寫能力、網絡傳輸處理能力及介質等),系統層及軟件能力,代碼質量(空間復雜度、時間復雜度)等。
##### 開發和修改的速度
使用設計模式就是為了解決開發和修改的速度。不至于使一個開發任務總是需要重構系統。
##### 團隊速度
團隊協作的開發任務存在速度問題。處理大而復雜的程序需要了解協商一個共同的計劃和目標,便于高效創建、維護大型程序。OOP和設計模式就有很多公用的東西可以加快團隊工作。設計模式提供了一種編程方法允許團隊中的程序員分頭工作,最后將工作匯聚在一起。就像一個生產線,每個小組負責不同的部件。小組之間需要一個開發模式理解不同部件的關系。
##### 21.1.3 順序和過程式編程有什么問題
##### 順序編程
```
<?php
$a = 10;
$b = 20;
$total = $a + $b;
echo $total;
?>
```
程序逐條執行(建立一系列代碼執行一個程序)稱為順序編程。
##### 過程式編程
```
<?php
function addTotal($a,$b)
{
$total = $a + $b;
echo $total;
}
addTotal(10,20);
?>
```
過程式編程通過引入函數,可以在任何一個地方利用一條語句調用某個操作完成一個程序序列。函數或過程允許程序員將代碼序列分為多個模塊,方便重用。
其類似OOP,但是沒有提供類。類對象可以處理自己的數據結構,這式函數無法單獨做到的。比如某個類對象可以在生成的時候初始化自己的初始屬性值,這是屬于它本身的數據結構;也可以在序列處理傳遞的過程中改變其屬性值,從而改變其內在的數據結構。過程式編程合作困難,不同的開發組成員無法采用OOP那樣輕松地處理相互關聯且獨立地類。
#### 21.2 OOP的基本概念
##### 21.2.1 抽象
抽象指示一個對象的基本特征,對其提供了清晰的定界以便它與其他對象區分開來。
所有類都是對數據的一組操作的抽象。
##### 抽象類和抽象方法
抽象類以abstract命名:
```asp
<?php
abstract class onePersonAbstract
{
public $age;
abstract public function shout($name);
public function study($curriculums)
{
echo $curriculums . "</br>";
}
}
```
抽象類的特點:
- 可以聲明屬性。
- 可以有抽象方法或者已實現的方法。
- 抽象類不能實例化只能被繼承。
- 繼承抽象類的子類必須實現抽象類的所有抽象方法才能實例化,否則也應加上abstract作為抽象類。
- 抽象類的子類也是抽象類的情況下,其抽象方法的訪問控制(public、protected、private)不可比父抽象類嚴格。
繼承抽象類的子類:
```
<?php
class onePersonExample extends onePersonAbstract
{
public function shout($name)
{
echo $name . "</br>";
}
public function study($curriculums)
{
echo $curriculums . "</br>";
}
}
$p = new onePersonExample;
$p->shout('李');
$p->study('數學');
```
這個子類繼承了抽象類onePersonAbstract的$age屬性以及study方法,并實現了study方法。并且子類同樣可以重寫父抽象類實現的方法。
:-: 
##### 21.2.2 特性Trait
PHP不允許多繼承,所以提供了一個Trait結構,稱為特性,是一種代碼重用機制。當一個類繼承另一個類,同時使用Trait,相當于多重繼承。
```
<?php
trait Dog{
public $name="dog";
public function bark(){
echo "This is dog";
}
}
class Animal{
public function eat(){
echo "This is animal eat";
}
}
class Cat extends Animal{
use Dog;
public function drive(){
echo "This is cat drive";
}
}
$cat = new Cat();
$cat->drive();
echo "<br/>";
$cat->eat();
echo "<br/>";
$cat->bark();
?>
```
:-: 
##### 21.2.3 接口interface
一般約定接口以為`I`開頭:
```
<?php
interface IMethodHolder
{
public function getInfo($info);
public function sendInfo($info);
public function calculate($first,$second);
}
```
接口的特點:
- 不能有具體實現的方法或聲明變量。
- 只負責定義功能,不負責具體實現,相當于方法是純粹的模板,且方法的訪問控制只能是public。
- 可以聲明常量。
- 實現接口的類必須實現接口的所有方法,否則應該作為抽象類加上abstract關鍵字。
- 一個類可以繼承多個接口(接口同樣可以繼承多個接口)。
繼承接口的類必須實現接口的方法,使用implements關鍵字,繼承多個接口以`,`隔開:
```
<?php
include_once('ImethodHolder.php');
class methodExample implements ImethodHolder
{
public function getInfo($info)
{
echo "This info is " . $info . "</br>";
}
public function sendInfo($info)
{
return $info;
}
public function calculate($first,$second)
{
$num = $first + $second;
return $num;
}
public function useMethod(){
$this->getInfo('message success');
echo $this->sendInfo('send message success');
}
}
$worker = new methodExample();
$worker->useMethod();
```
:-: 
繼承接口的類除了實現接口必須實現的方法外,還可以更具需要增加更多方法和屬性。
##### 21.2.4 封裝
來自O'REILLY《PHP設計模式》對封裝的解釋是:`封裝是劃分一個抽象的諸多元素的過程,這些元素構成該抽象的結構和行為;封裝的作用是將抽象的契約接口與其實現分離`。
其通俗的意思是:封裝提供了標準的契約接口將復雜的實現過程透明化。例子:你要使用汽車,汽車由很多對象構成,比如內燃機,機械控制系統,傳動器,計算機程序等。你并不了解這些運行的原理,但是你只需要擰動鑰匙就可以發動汽車,這就是封裝了使用汽車的過程。放在編程上面的例子:我想知道運行信息info,運行時有多種復雜的對象構成,比如請求解析,響應生成,數據結構創建維護,緩沖區維護,事件響應,格式生成等。但是我只需要得到一個標準的運行信息info,于是我將前面的方法組合在一起加以修飾,封裝了一個getInfo()方法,可以得到標準信息。
##### 利用控制訪問的封裝
利用控制訪問可以很好的對程序進程封裝。
- private:僅內部可以訪問。
- protected:僅內部和繼承者可以訪問。
- public:公開的,用于對內外都可以使用。可以作為契約標準提供封裝的服務。
##### 21.2.5繼承
如果一個類擴展了另一個類,就會擁有這個類的所有屬性和方法。
##### 21.2.6 多態
調用相同的接口(封裝程序)可以完成不同的工作。
多態例子:
```
<?php
include_once('mongoDb.php');
include_once('mysql.php');
class db
{
function __construct($dbname)
{
$db = new $dbname;
return $db;
}
}
$mongoDb = new db('mongoDb');
$mysql = new db('mysql');
```
上面的代碼通過一個公共接口(__construct的控制訪問屬于public)可以實現不同的工作。
多態例子:
```
<?php
interface Imethod
{
public getNum($num);
public setNum($num);
}
class bank implements Imethod
{
public $bankNum;
public getNum($num)
{
return $num;
}
public setNum($num)
{
return $this->bankNum = $num;
}
}
class restaurant implements Imethod
{
public $restaurantNum;
public getNum($num)
{
if(is_int($num)){
return $num;
}
return "不是整數";
}
public setNum($num)
{
if(is_int($num)){
return $this->restaurantNum = $num;
}
return "不是整數";
}
}
```
bank和restaurant同樣實現了Imethod接口。但是它們的內部方法實現的功能不一樣。
#### 21.3 MVC
MVC指的是`模型-視圖-控制器`設計模式。可以實現`松耦合`和`重新聚焦`。
- 模型:負責數據部分,處理企業和應用邏輯。
- 視圖:作為窗口顯示需要顯示的信息。例如最終需要顯示出來的頁面、圖片、文本等信息都是視圖的一部分。
- 控制器:控制器調用模型、視圖,處理好其邏輯和業務順序,并關聯其上下文,達到控制數據和表現信息的目的。
在很多PHP框架代碼中,將代碼分為controller(控制器層)、data-dao-model(model負責調用db的方法、dao負責對db的讀寫方法,data負責從db拿出數據的應用邏輯算法,總體為模型層)、view(視圖層)。其控制器層負責調用模型層和視圖層,用以應用邏輯生成結果反饋用戶。
```
<?php
class numController
{
//控制器接口addNum
public function addNum($id,$num)
{
$data = new numData;
//控制器負責組織模型、視圖的業務邏輯和調用順序,最終將結果反饋
$totalNum = $data->addNum($id,$num); //控制器調用模型層經過內部封裝的方法實現應用邏輯
View::assign('totalNum',totalNum); //控制器調用視圖層將結果反饋用戶
}
}
```
通過MVC,一個控制器類可以實現多個方法;一個模型層類可以實現多種數據操作方法。通過組合可以使其能解決數個最小子問題,且代碼不重復實現了松耦合。試想如果每遇到一個小問題就要重復代碼,對研發效率和可維護性都是挑戰;同時可以聚焦在具體的小問題上,通過定制的控制器調用專用的最小數量最小作用的數據處理方法解決問題以專用的視圖方法將結果反饋。
#### 21.4 設計模式原則
##### 按接口而不是按實現來編程
設計一個類的時候不是為了應用邏輯而按照應用邏輯的具體實例設計,而是按照接口的方式設計為抽象類或者接口數據類型的實例。這樣設計的才是一個能解決共同特征問題的類,而不僅是一個僅能解決本次實現的實例,前者可以解耦,后者會使得設計和代碼冗余。
##### 盡量選擇對象組合而不是類繼承
子類通過組合實現多個類的功能,而不是僅通過繼承父類,同樣都可以進行功能擴展。其缺點是:依賴關系比繼承復雜。其優點更加突出:避免維護多個繼承層次的類,避免更改上層層次導致的錯誤;子類不會因為繼承到大量的不用的屬性和方法變得臃腫;使用組合可以將一個類的任務傳遞給另一個類,稱為"委托",使用組合可以使用一個類或者一組類完成一系列任務。所以設計模式方法建議使用`淺繼承`。
##### 設計模式的組織
設計模式按作用分類:
1. 創建型模式:組合一組靈活的行為到更為復雜的集合中。提供一些方法封裝這些行為組合。
例如有一個類是負責請求處理,另一個類負責響應處理,需要另外一個類隱式地既處理請求又處理響應,就需要用創建型模式將兩個類組合并使其行為組織到另外一個類中,這時可以創建新的方法。
2. 結構型模式:保證組合結構的結構化。類通過繼承來組合接口或實現,對象通過組合對象使之產生關聯來建立新功能的方法。
3. 行為型模式:負責算法和對象之間職責的分配。
按照范圍分類:
1. 類模式:重點在于類與子類的關系。
2. 對象模式:與類模式的區別在于對象模式強調的是可以在運行時改變對象具有動態性。
| 類型 | 范圍 | 模式名 | 可能變化的方面 |
| ------ | ------------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------------ |
| 創建型 | 類<br />對象 | 工廠方法<br />原型 | 實例化對象的子類<br />實例化對象的類 |
| 結構型 | 類<br />對象 | 適配器*<br />適配器*<br />裝飾器 | 對象的接口<br />對象職責而不是派生類 |
| 行為型 | 類<br />對象<br />對象<br />對象<br />對象 | 模板方法<br />狀態<br />策略<br />職責鏈<br />觀察者 | 算法中的步驟<br />對象狀態<br />算法<br />可以滿足的對象<br />依賴于其他對象的對象數;當前可以有多少個依賴對象 |
? 設計模式作用、范圍和變化
##### 關系
設計模式里有多種關系。
##### 相識關系
一個參與者包含另一個參與者的引用。
```
<?php
class A extends base
{
private $obRequest;
protected function request()
{
$this->obRequest = new obRequest;
$this->obRequest->request();
}
public function pass($str)
{
//口令
if($str == 'oqshhhasdh'){
$this->request();
}
print('$str error');
}
}
```
A的方法中包含了obRequest的引用。
##### 聚合關系
和相識關系類似,不過生命周期相同。
```
<?php
class A extends base
{
private $obRequest;
public __construct(obRequest $obRequest){
$this->obRequest = new obRequest;
}
public function pass($str)
{
//口令
if($str == 'oqshhhasdh'){
$this->obRequest->request();
}
print('$str error');
}
}
```
實例化A的時候構造函數就實例化了obRequest,使兩個類擁有同樣的生命周期。
##### 創建關系
一個對象創建另一個對象的實例。
#### 21.5 UML統一建模語言
UML是一種為面向對象系統的產品進行說明、可視化和編制文檔的一種標準語言,是非專利的第三代建模和規約語言。UML是面向對象設計的建模工具,獨立于任何具體程序設計語言。使用UML能夠幫助更好地設計程序。

? UML類圖
:-: 
? 對象圖
:-: 
? 交互圖
PHP有UML擴展可以生成類關系圖,有興趣地同學可以自行學習。
#### 21.6 創建型設計模式
創建型設計強調實例化過程,設計時目的是隱藏實例創建過程,封裝實例內部邏輯,其模式包括:
- 抽象共創
- 生成器
- 工廠方法
- 原型
- 單例
##### 21.6.1 工廠方法設計模式
當一個類創建對象數量固定或需要創建有類似特征數量不定的對象時,可以考慮工廠方法設計模式。
工廠模式例子:
- 工廠抽象類
```
<?php
abstract class Creator
{
protected abstract function factoryMethod();
public function startFactory()
{
$mfg = $this->factoryMethod();
return $mfg;
}
}
```
- 產品接口
```
<?php
interface product
{
public function getProperties();
}
```
- 輸出文字的工廠
```
<?php
include_once(Creator.php);
include_once(textProduct.php);
class textFactory extends Creator
{
protected function factoryMethod()
{
$product = new textProduct();
return($product->getProperties());
}
}
```
- 輸出圖片的工廠
```
<?php
include_once(Creator.php);
include_once(graphicProduct.php);
class graphicFactory extends Creator
{
protected function factoryMethod()
{
$product = new graphicProduct();
return($product->getProperties());
}
}
```
- 文字產品
```
<?php
include_once(product.php);
class textProduct implements product
{
private $mfgProduct;
public function getProperties()
{
$this->mfgProduct = "這是文字產品";
return $this->mfgProduct;
}
}
```
- 圖片產品
```
<?php
include_once(product.php);
class graphicProduct implements product
{
private $mfgProduct;
public function getProperties()
{
$this->mfgProduct = "這是圖片產品";
return $this->mfgProduct;
}
}
```
- 客戶
```
<?php
include_once(graphicFactory.php);
include_once(testFactory.php);
class client
{
private $someGraphicObject;
private $someTextObject;
public function __construct()
{
$this->someGraphicObject = new graphicFactory();
echo $this->someGraphicObject->factoryMethod();
$this->someTextObject = new textFactory();
echo $this->someTextObject->factoryMethod();
}
}
```
此客戶類需要同時輸出圖片和文字產品。所以它將圖片工廠和文字工廠聚合與自己聚合。并由文字工廠生產文字,圖片工廠生產圖片。如果此時只需要圖片產品:
```
<?php
include_once(graphicFactory.php);
include_once(testFactory.php);
class client
{
private $someGraphicObject;
private $someTextObject;
public function __construct()
{
$this->someGraphicObject = new graphicFactory();
echo $this->someGraphicObject->getProperties();
}
}
```
此時只需要把文字工廠去掉。并且由于產品方法getProperties()有完全不同的實現,有多態的作用。
##### 一個工廠多個產品
這樣的工廠可以創建不同的產品,例如:圖片工廠可以產生不同的圖片形式、呈現效果。
- 工廠抽象類
```
<?php
abstract class Creator
{
protected abstract function factoryMethod(product $product);
public function doFactory($productNow)
{
$mfg = $this->factoryMethod($productNow);
return $mfg;
}
}
```
- 工廠
```
<?php
include_once(Creator.php);
class factory extends Creator
{
private $country;
protected function factoryMethod(product $product)
{
$this->country = $product;
return($this->country->getProperties());
}
}
```
- 產品1
```
<?php
include_once(product.php);
class product1 implements product
{
private $mfgProduct;
public function getProperties()
{
$this->mfgProduct = "產品1";
return $this->mfgProduct;
}
}
```
- 產品2
```
<?php
include_once(product.php);
class product2 implements product
{
private $mfgProduct;
public function getProperties()
{
$this->mfgProduct = "產品2";
return $this->mfgProduct;
}
}
```
這樣通過一個工廠我們可以創建多種產品:
```
<?php
include_once(factory.php);
class client
{
private $factory;
public function __construct()
{
$this->factory = new factory();
echo $this->factory->doFactory(new product1);
echo $this->factory->doFactory(new product2);
}
}
```
這樣可以根據需要增加產品和創建產品。
在前面PHP的章節我我們講過一個簡單的工廠模式,我們來回憶以下:
```
<?php
class client
{
private $db;
function __construct($dbClass,$info)
{
$this->db = new factoryDb($dbClass,$info);
}
public function insert($arr)
{
try{
$this->insert($arr);
}catch(Exception $e){
echo "寫入數據錯誤";
}
}
}
//mysql
$db1 = new client('mysql',['user1','passwd1']);
$db1->insert(['level'=>1,','apple']);
//mongodb
$db2 = new client('mongodb',['user2','passwd2']);
$db2->insert(['age'=>1,['color'=>'red','price']]);
```
##### 21.6.2 原型設計模式
需要創建某個原型對象的多個實例時就要使用原型模式。例如游戲開發中可以通過克隆一個原型士兵增加軍隊的人數和兵種。
```
<?php
abstract class humans
{
public $name;
public $photo;
}
class person extends humans
{
public function __construct()
{
$this->name = '大兵';
$this->photo = 'soldier.png' . '</br>';
}
public function exec()
{
echo "這是" . $this->name . '</br>';
echo $this->photo;
}
}
$soldier1 = new person();
$soldier1->exec();
$soldier2 = clone $soldier1;
$soldier2->name = '小兵';
$soldier2->exec();
$soldier1->exec();
```
注意:克隆不會啟動構造函數。
:-: 
##### 現代企業組織的原型設計模式應用
現代企業系統復雜而龐大,但又有很多共同特征。可以利用原型設計模式設計程序。例如設計工程部負責創建產品,管理部負責處理資源協調和組織、市場部負責銷售和推廣。
#### 21.7 結構型設計模式
結構型模式研究組合對象和類構成更大的結構。部分結構型設計模式:
- 適配器模式(類和對象)
- 橋接模式
- 組合模式
- 裝飾器模式
- 外觀模式
- 享元模式
- 代理模式
##### 21.7.1 適配器模式
在需要的場景下提供對應適配的模塊就是適配器模式。例如:PC端和手機端分別為不同的頁面呈現。當我們研發了一個PHP系統可以做各種工作(數據庫處理、頁面布局、圖片展示等),但是僅僅做了PC端的頁面。為了制作移動端頁面我們不能將所有的程序推翻重做或者另外再做一套系統,而是應該適配上移動頁面,使之兼容原系統功能且能有另外的頁面呈現。
頁面呈現適配例子:
- PC處理類
```
<?php
class pc
{
private $info;
private $width;
public function request($info,$width)
{
$this->info = $info;
$this->width = $width;
return $this->view();
}
}
```
- 移動端處理類
```
<?php
class moble
{
private $info;
private $width;
public function request($info,$width)
{
$this->info = $info;
$this->width = $width;
return $this->view();
}
}
```
- 頁面處理適配接口
```
<?php
interface Itarget
{
function infoHandler(); //用于不同場景的不同處理
}
```
- 移動端適配器
```
<?php
class mobleAdapter extends moble implements Itarget
{
public function infoHandler(){
View::assign('info',$this->info);
View::fetch('moble');
}
}
```
移動端適配器既繼承了移動端處理類的方法,也實現了移動端基于頁面適配接口的特定方法,并沒有影響PC類和PC處理類的表現。對對象適配模式有興趣的同學可以自行學習:比如通過一個適配器傳入pc處理類或者移動處理類就能分別處理pc和移動。
##### 21.7.2裝飾器模式
裝飾器模式向現有的對象增加對象,有時候稱為`包裝器`。需要對現有對象增加新功能而又不想影響其他對象時可以使用裝飾器模式。
添加商品計算總價的包裝器例子:
- 包裝基礎抽象類
```
<?php
abstract class Icomponent
{
protected $things;
abstract public function getThings();
abstract public function getPrice();
}
```
- 裝飾器接口
```
<?php
abstract class Decotator extends Icomponent
{
abstract public function getThings();
abstract public function getPrice();
//可以實現按照需要增加方法和屬性
}
```
- 組件
```
<?php
class BasicThings extends Icomponent
{
public function __construct()
{
$this->things = 'Basic Things' . '</br>';
}
public function getThings()
{
return $this->things;
}
public function getPrice()
{
return 10;
}
}
```
- 裝飾器(a)
```
<?php
class a extends Decotator
{
public function __construct(Icomponent $thingNow)
{
$this->things = $thingNow;
}
public function getThings()
{
return $this->things->getThings() . 'a';
}
public function getPrice()
{
return 12 + $this->things->getPrice();
}
}
```
- 裝飾器(b)
```
<?php
class b extends Decotator
{
public function __construct(Icomponent $thingNow)
{
$this->things = $thingNow;
}
public function getThings()
{
return $this->things->getThings() . 'b';
}
public function getPrice()
{
return 13 + $this->things->getPrice();
}
}
```
- 裝飾器(c)
```
<?php
class c extends Decotator
{
public function __construct(Icomponent $thingNow)
{
$this->things = $thingNow;
}
public function getThings()
{
return $this->things->getThings() . 'c';
}
public function getPrice()
{
return 15 + $this->things->getPrice();
}
}
```
- 使用組件并按需求添加裝飾器
```
<?php
class Client
{
private $things;
public function __construct()
{
$this->things = new BasicThings();
$this->things = $this->wrapComponent($this->things);
echo $this->things->getThings();
echo $this->things->getprice();
}
/*
c的$things屬性是b對象,b的$things屬性是a對象,a的$things屬性是BasicThings對象
再配合裝飾對象的鏈式調用方法可以增加裝飾對象或者減少裝飾對象來增減其功能
*/
private function wrapComponent(Icomponent $component)
{
$component = new a($component);
$component = new b($component);
$component = new c($component);
return $component;
}
}
$worker = new Client();
```
這樣隨時可以在組件BasicThings對象里增減'abcdefg'等功能對象。可以把組件BasicThings想象成一個巨大的空間,里面可以隨意組合各種功能。
:-: 
回憶一下數據結構隊列的知識,組件對象添加裝飾對象,并且執行裝飾對象功能的順序是不是按照先添加的先執行的順序執行程序的呢?上面組件按照abc順序添加裝飾,并且先調用組件基礎功能后按照abc順序調用裝飾的功能。
上面是單組件的裝飾器實現,對于多組件的裝飾器實現,只需要在添加組件再按需要實例化相應一個或者多個組件完成工作。對于多組件的具體裝飾器可以為一個裝飾器對應一個功能選項或一個裝飾器對應多個功能選項。
##### 總結
程序越龐大,OOP編程和結構性設計模式就月有用。
#### 21.8 行為型設計模式
行為型設計模式強調參與者之間的通信,部分模式名稱:
- 職責鏈模式
- 命令模式
- 解釋器模式
- 迭代器模式
- 中介這模式
- 備忘錄模式
- 觀察者模式
- 狀態模式
- 模板方法模式(類設計模式)
- 策略模式
- 訪問者模式
##### 21.8.1 模板方法模式
如果明確算法中的步驟,步驟可以采用不同的方法實現時,可以采用模板方法模式。模板方法模式一般由一個抽象類和一個或多個實現抽象類的具體類組成。
例如一個計算數字的例子:
- 模板方法抽象類
```
<?php
abstract class NumberMath
{
protected $first;
protected $second;
public function templateMethod($num1,$num2)
{
$this->first = $num1;
$this->second = $num2;
$this->addNum();
$this->multiplyNum();
}
abstract protected function addNum();
abstract protected function multiplyNum();
}
```
- 模板方法具體實現類
```
<?php
class NumberOne extends NumberMath
{
protected function addNum()
{
echo($this->$first + $this->$second);
}
protected function multiplyNum()
{
echo($this->$first * $this->$second);
}
}
```
- 客戶
```
<?php
class Client
{
function __construct()
{
$num1 = 10;
$num2 = 20;
$numObject = new NumberOne();
$numObject->templateMethod(num1,$num2);
}
}
$worker = new Client();
```
在模板方法抽象類中templateMethod方法已經確定算法步驟的情況下,可以有不同的模板方法具體實現類封裝不同的步驟方法供這個算法步驟使用。也就是說模板方法具體實現類可以有一個或者多個,因為算法的步驟是固定的,只是算法的實現不同。
##### 21.8.2 職責鏈模式
需要將一個對象經過一個個步驟進行處理返回最終對象,并且步驟的數量順序是可以調整時,可以考慮使用職責鏈模式。我們在PHP的框架中經常遇到稱為中間件(或者鉤子)的過程,它就是使用了職責鏈模式:會根據情況添加特殊處理的中間件程序'abcde...',對象由a處理后返回給b處理再返回給c處理直到中間件程序全部處理完成交由下一個程序步驟,這就是職責鏈。從一個場景去理解職責鏈,假如你在一家公司有時候需要申請辦公金用于工作,1000以內要上報小組長,1000-3000以上要上報VP,3000以上要上報總經理。如果你要申請3100元的辦公金,你首先要上報小組長,小組長審批通過后會上報VP,VP審批通過后會上報總經理,總經理審批通過后才能使用這3100元。并且在任何環節都可以有特殊處理甚至有中斷權,比如小組長審批時可以直接拒絕,這個申請就不會傳到VP或者總經理那一級去。
##### 21.8.3 狀態設計模式
狀態模式允許對象改變狀態時改變其行為,是一種最有用的模式。比如游戲角色的狀態改變導致的動作變化。在PHP程序中狀態改變太依賴條件語句的情況下會造成程序混亂,成為負擔。此時從另外一個角度"對象"的狀態考慮,就不需要查看對象的控制流,使之"以狀態為中心"。
##### 理解狀態機
觸發器請求一個改變后發生變遷,變遷是及時的可能很快也可能有很復雜的過程,最后達到另外一個狀態。
狀態模型的本質:
- 狀態
- 變遷
- 觸發器
將一個臺燈比作狀態機:開燈關燈就是狀態,觸發器就是開關,變遷就是觸發器控制的開燈關燈狀態變化的過程。
狀態機例子:
- 上下文信息類
```
<?php
class Context
{
private $offState; //用以保存關狀態對象
private $onState; //用以保存開狀態對象
private $currentState; //當前狀態對象
public function __construct()
{
$this->offState = new OffState($this);
$this->onState = new OnState($this);
//開始狀態設置為Off
$this->currentState = $this->offState;
}
//觸發器方法 開
public function turnOnlihgt()
{
$this->currentState->turnLightOn();
}
//觸發器方法 關
public function turnOfflihgt()
{
$this->currentState->turnLightOff();
}
//設置狀態
public function setState(IState $state)
{
$this->currentState->$state;
}
//獲得開狀態
public function getOnState()
{
return $this->onState;
}
//獲得關狀態
public function getOnState()
{
return $this->offState;
}
}
```
上下文類用以保存所有的狀態和當前狀態,以及提供設置/獲取/觸發狀態的方法。
- 狀態接口
```
<?php
interface IState
{
public function turnLightOn();
public function turnLightOff();
}
```
狀態接口依據所有狀態指定必須完成的狀態處理抽象方法,供狀態類必須完成。
- 開狀態類
```
<?
class OnState implements IState
{
$private $context;
public function __construct($contextNow)
{
$this->context = $contextNow;
}
public function turnLightOn()
{
echo "已經打開";
}
public function turnLightOff()
{
$this->context->setState($this->context->getOffState);
}
}
```
- 關狀態類
```
<?
class OffState implements IState
{
$private $context;
public function __construct($contextNow)
{
$this->context = $contextNow;
}
public function turnLightOn()
{
$this->context->setState($this->context->getOnState);
}
public function turnLightOff()
{
echo "已經關閉";
}
}
```
- 客戶
```
<?php
class Client
{
private $context;
public function __construct()
{
$this->context = new Context();
$this->context->turnOnlihgt();
$this->context->turnOnlihgt();
$this->context->turnOflihgt();
$this->context->turnOflihgt();
}
}
```
:-: 
##### 添加狀態
如何讓狀態機記錄并處理更多的狀態。
- 上下文類記錄更多狀態對象。
- 狀態接口制定更多狀態處理方法。
- 具體狀態實現狀態接口里每種狀態變遷的方法,并做好狀態邊界檢測(比如燈關了不能再關,飛行器左邊界不能再向左走)。
為上面可以添加顏色狀態(白、藍、黃光),有興趣的同學可以自己試試。
#### 21.9 代理模式
代理模式強調的是請求到達真實對象前先經過"守門人"。"守門人"會在此之間做檢測、判斷、分派、信息處理、特殊處理等。
- 請求接口
```
<?php
interface ISubject
{
function request();
}
```
- 代理(守門人)
```
<?php
class proxy implements ISubject
{
public function check()
{
//code
}
public function request()
{
$this->relaSubject = new RelaSubject();
$this->relaSubject->request();
}
}
```
- 真實對象
```
<?php
class RelaSubject implement ISubject
{
public function request()
{
echo "請求成功";
}
}
```
- 客戶
```
<?php
class Client
{
private $proxy;
function __construct()
{
//偽代碼
$this->proxy = new proxy();
if($this->proxy->check() === true){
$this->proxy->request();
}else{
echo "信息錯誤不能傳入";
}
}
}
```