PHP三層結(jié)構(gòu)(下) PHP實(shí)現(xiàn)AOP
不過先不要忙著高興,有個(gè)問題就在這個(gè)可擴(kuò)展性里。當(dāng)新的需求被提出之后,我們可以再添加 ILWordExtension 實(shí)現(xiàn)類,這個(gè)的確正確。但是將這個(gè)新類注冊到getExtArray函數(shù)里,等于說還是要修改主業(yè)務(wù)邏輯代碼。能不能不修改呢?每次有新的需求變化還是要告知主業(yè)務(wù)邏輯,這樣終歸不太好。最理想的情況是新的擴(kuò)展代碼加入系統(tǒng)之后,主業(yè)務(wù)邏輯代碼不用修改,因?yàn)橹鳂I(yè)務(wù)邏輯根本不知道有新擴(kuò)展這回事!為此我們還需要優(yōu)化一下設(shè)計(jì)方案,如圖2所示:

(圖2),加入擴(kuò)展家族類
對于調(diào)用擴(kuò)展的主程序(也就是中間服務(wù)類LWordServiceCore),只讓它知道有ILWordExtension(擴(kuò)展)這件事就可以了,它不需要知道還有CheckPowerExtension(檢查權(quán)限擴(kuò)展)、CheckContentExtension(檢查內(nèi)容擴(kuò)展)和AddScoreExtension(加分?jǐn)U展)這三個(gè)類。對這三個(gè)類的調(diào)用過程被移動(dòng)到LWordExtensionFamily (擴(kuò)展家族類)里去了。
LWordExtensionFamily其實(shí)就是一個(gè)能存放多個(gè)ILWordExtension接口實(shí)例的容器類,從圖2中可以看出這個(gè)容器類不僅僅是實(shí)現(xiàn)了ILWordExtension接口,而且還聚合多個(gè)ILWordExtension接口的實(shí)例,所以它很特殊!對于LWordServiceCore類,這個(gè)類只知道ILWordExtension接口,但并不知道這個(gè)接口存在三個(gè)實(shí)現(xiàn)類。恰好LWordExtensionFamily類就實(shí)現(xiàn)了ILWordExtension接口,這很好的符合了中間服務(wù)類的要求,并且這個(gè)擴(kuò)展家族類知道ILWordExtension存在三個(gè)實(shí)現(xiàn)類,并會(huì)一一調(diào)用它們, LWordExtensionFamily代碼大概如代碼8所示:
// 代碼 8, 擴(kuò)展家族
// 擴(kuò)展家族
class LWordExtensionFamily implements ILWordExtension {
// 擴(kuò)展數(shù)組
private $_extensionArray = array();
// 添加擴(kuò)展
public function addExtension(ILWordExtension $extension) {
$this->_extensionArray []= $extension;
}
// 添加留言前
public function beforeAppend($newLWord) {
foreach ($this->_extensionArray as $extension) {
$extension->beforeAppend($newLWord);
}
}
// 添加留言后
public function behindAppend($newLWord) {
foreach ($this->_extensionArray as $extension) {
$extension->behindAppend($newLWord);
}
}
}
通過代碼8不難看出LWordExtensionFamily類雖然也實(shí)現(xiàn)了ILWordExtension接口,但是它并不做任何實(shí)質(zhì)的操作,而是通過循環(huán)語句將調(diào)用過程一一傳遞下去。為了平滑實(shí)現(xiàn)擴(kuò)展到插入的方式,所以最好創(chuàng)建一個(gè)工廠類MyExtensionFactory。如代碼9所示:
// 代碼 9
// 自定義擴(kuò)展工廠
class MyExtensionFactory {
// 創(chuàng)建留言擴(kuò)展
public static function createLWordExtension() {
$lwef = new LWordExtensionFamily();
// 添加擴(kuò)展
$lwef->addExtension(new CheckPowerExtension());
$lwef->addExtension(new CheckContentExtension());
$lwef->addExtension(new AddScoreExtension());
return $lwef;
// 注意這里返回的是擴(kuò)展家族類對象,
// 擴(kuò)展家族 LWordExtensionFamily 恰好也實(shí)現(xiàn)了接口 ILWordExtension,
// 所以這是符合業(yè)務(wù)邏輯的要求.
// 從此, 業(yè)務(wù)邏輯可以不關(guān)心具體的擴(kuò)展對象, 只要知道擴(kuò)展家族即可
}
}
使用擴(kuò)展工廠類的好處就是可以隨意的添加和移除擴(kuò)展實(shí)例,這就很好的實(shí)現(xiàn)了可插入式編程。對于LWordServiceCore類只知道一個(gè)ILWordExtension接口,對于LWordExtensionFamily知道需要一一調(diào)用每個(gè)擴(kuò)展,但是具體會(huì)有多少個(gè)擴(kuò)展是通過MyExtensionFactory給出的。各負(fù)其責(zé)結(jié)構(gòu)也很清晰。如果我們做一個(gè)假設(shè),MyExtensionFactory類的createLWordExtension函數(shù)不是通過new關(guān)鍵字這樣的硬編碼方式來添加擴(kuò)展列表,而是通過更巧妙的讀取配置文件的方式來得到擴(kuò)展列表,那么是不是更方便更靈活呢?不過這個(gè)就不再本文中討論了。
中間服務(wù)層通過工廠類取得一個(gè)ILWordExtension接口的具體實(shí)例,然后調(diào)用其beforeAppend和behindAppend方法。當(dāng)然中間服務(wù)并不知道工廠類返回的其實(shí)是一個(gè)含有多個(gè)ILWordExtension實(shí)例的容器(因?yàn)檫@個(gè)容器也實(shí)現(xiàn)了ILWordExtension接口),所以中間服務(wù)也就不知道擴(kuò)展是被一一調(diào)用的。完整代碼如代碼10所示:
// 代碼 10, 完整代碼
// 擴(kuò)展接口
interface ILWordExtension {
// 添加留言前
public function beforeAppend($newLWord);
// 添加留言后
public function behindAppend($newLWord);
};
// 檢查權(quán)限
class CheckPowerExtension implements ILWordExtension {
// 添加留言前
public function beforeAppend($newLWord) {
// 在這里判斷用戶權(quán)限
}
// 添加留言后
public function behindAppend($newLWord) {
}
};
// 檢查留言文本
class CheckContentExtension implements ILWordExtension {
// 添加留言前
public function beforeAppend($newLWord) {
if (stristr($newLWord, "fuck"))
throw new Exception();
}
// 添加留言后
public function behindAppend($newLWord) {
}
};
// 用戶積分
class AddScoreExtension implements ILWordExtension {
// 添加留言前
public function beforeAppend($newLWord) {
}
// 添加留言后
public function behindAppend($newLWord) {
// 在這里給用戶積分
}
};
// 擴(kuò)展家族
class LWordExtensionFamily implements ILWordExtension {
// 擴(kuò)展數(shù)組
private $_extensionArray = array();
// 添加擴(kuò)展
public function addExtension(ILWordExtension $extension) {
$this->_extensionArray []= $extension;
}
// 添加留言前
public function beforeAppend($newLWord) {
foreach ($this->_extensionArray as $extension) {
$extension->beforeAppend($newLWord);
}
}
// 添加留言后
public function behindAppend($newLWord) {
foreach ($this->_extensionArray as $extension) {
$extension->behindAppend($newLWord);
}
}
}
// 自定義擴(kuò)展工廠
class MyExtensionFactory {
// 創(chuàng)建留言擴(kuò)展
public static function createLWordExtension() {
$lwef = new LWordExtensionFamily();
// 添加擴(kuò)展
$lwef->addExtension(new CheckPowerExtension());
$lwef->addExtension(new CheckLWordExtension());
$lwef->addExtension(new AddScoreExtension());
return $lwef;
}
}
// 中間服務(wù)層
class LWordServiceCore implements ILWordService {
// 添加留言
public function append($newLWord) {
// 獲取擴(kuò)展
$ext = MyExtensionFactory::createLWordExtension();
$ext->beforeAppend($newLWord);
// 調(diào)用數(shù)據(jù)訪問層
$dbTask = new LWordDBTask();
$dbTask->append($newLWord);
$ext->behindAppend($newLWord);
}
};
從代碼10中可以看出雖然CheckPowerExtension、CheckContentExtension、AddScoreExtension以及LWordExtensionFamily都實(shí)現(xiàn)了ILWordExtension接口,但是它們的beforeAppend和behindAppend函數(shù)過程卻完全不同!特別是LWordExtensionFamily擴(kuò)展家族類,它并沒有實(shí)質(zhì)的業(yè)務(wù)邏輯處理過程,而是將調(diào)用依次傳遞給每一個(gè)擴(kuò)展。beforeAppend和behindAppend函數(shù)在具體類中的不同實(shí)現(xiàn),這是面向?qū)ο蟪绦蛟O(shè)計(jì)中的很典型的特性:多態(tài)!
將次業(yè)務(wù)邏輯分散到各個(gè)擴(kuò)展中,這種做法已經(jīng)非常近似AOP(Aspect OrientedProgramming,面向切面編程)的編程方式。權(quán)限校驗(yàn)、內(nèi)容檢查和積分可以看作是不同的切面,這些切面和主業(yè)務(wù)邏輯交叉在一起,但又不會(huì)影響到主業(yè)務(wù)邏……這樣做的好處就是擴(kuò)展代碼不會(huì)干擾主業(yè)務(wù)邏輯,我們也可以針對某一個(gè)擴(kuò)展進(jìn)行編碼和單元測試,然后通過MyExtensionFactory工廠類把擴(kuò)展插入到業(yè)務(wù)流程中。完整的執(zhí)行過程如圖3所示:

(圖3),執(zhí)行流程
本文源碼下載地址:http://xiazai.jb51.net/201007/yuanma/TraceLWord.rar
相關(guān)文章
php 備份數(shù)據(jù)庫代碼(生成word,excel,json,xml,sql)
本篇文章是對php備份數(shù)據(jù)庫代碼(生成word,excel,json,xml,sql)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06php select,radio和checkbox默認(rèn)選擇的實(shí)現(xiàn)方法
radio和checkbox默認(rèn)選擇的實(shí)現(xiàn)方法,大家參考下原理就知道了,不論asp,asp.net,jsp都是這個(gè)原理。2010-05-05PHP封裝的微信公眾平臺(tái)接口開發(fā)操作類完整示例
這篇文章主要介紹了PHP封裝的微信公眾平臺(tái)接口開發(fā)操作類,結(jié)合完整實(shí)例形式分析了php微信開發(fā)所涉及的配置、驗(yàn)證、接收、響應(yīng)、文本、語音、圖片等相關(guān)操作技巧,需要的朋友可以參考下2018-06-06如何在舊的PHP系統(tǒng)中使用PHP 5.3之后的庫
這篇文章主要介紹了如何在舊的PHP系統(tǒng)中使用PHP 5.3之后的庫,需要的朋友可以參考下2015-12-12php+jQuery遞歸調(diào)用POST循環(huán)請求示例
這篇文章主要介紹了php+jQuery遞歸調(diào)用POST循環(huán)請求,結(jié)合實(shí)例形式分析了php+jQuery的ajax方法遞歸調(diào)用與json轉(zhuǎn)換技巧,需要的朋友可以參考下2016-10-10PHP自定義遞歸函數(shù)實(shí)現(xiàn)數(shù)組轉(zhuǎn)JSON功能【支持GBK編碼】
這篇文章主要介紹了PHP自定義遞歸函數(shù)實(shí)現(xiàn)數(shù)組轉(zhuǎn)JSON功能,針對json_encode函數(shù)處理GBK編碼中文出現(xiàn)亂碼的情況,使用自定義函數(shù)進(jìn)行數(shù)組遞歸遍歷實(shí)現(xiàn)可兼容GBK編碼的數(shù)組轉(zhuǎn)json功能,需要的朋友可以參考下2018-07-07php數(shù)組函數(shù)序列 之a(chǎn)rray_count_values() 統(tǒng)計(jì)數(shù)組中所有值出現(xiàn)的次數(shù)函數(shù)
array_count_values() 函數(shù)用于統(tǒng)計(jì)數(shù)組中所有值出現(xiàn)的次數(shù),本函數(shù)返回一個(gè)數(shù)組,其元素的鍵名是原數(shù)組的值,鍵值是該值在原數(shù)組中出現(xiàn)的次數(shù)。2011-10-10用PHP實(shí)現(xiàn)的四則運(yùn)算表達(dá)式計(jì)算實(shí)現(xiàn)代碼
題目要求:有一個(gè)四則運(yùn)算的字符串表達(dá)式,編寫一個(gè)函數(shù),計(jì)算四則運(yùn)算的結(jié)果2011-08-08關(guān)于PHP文件的自動(dòng)運(yùn)行方法分析
這篇文章主要介紹了PHP文件的自動(dòng)運(yùn)行方法,分析了兩種自動(dòng)刷新的方法及相應(yīng)的優(yōu)缺點(diǎn),需要的朋友可以參考下2016-05-05