新聞中心
php高級特性-反射以及工廠設(shè)計(jì)模式的結(jié)合使用 [結(jié)合 Laravel-Admin 代碼實(shí)例講解]

利用反射來實(shí)現(xiàn)工廠模式的生產(chǎn)而無需創(chuàng)建特定的工廠類
反射[Relfection]
什么是Reflection
Reflection,即反射。反射提供給面向?qū)ο缶幊炭梢宰允〉哪芰?/p>
這么理解有點(diǎn)太過于概念化,通俗地講,就是能根據(jù)事件的結(jié)果反查出原因。在編程中,可以根據(jù)一個(gè)被實(shí)例化的對象,反查出這個(gè)對象屬于的類以及該類擁有所有屬性以及方法,甚至可以讀取文檔注釋。這個(gè)反查的過程就叫做反射【推薦學(xué)習(xí):PHP視頻教程】
PHP 提供了完整的反射 API ,提供了內(nèi)省類、接口、函數(shù)、方法和擴(kuò)展的能力。此外,反射 API 提供了方法來取出函數(shù)、類和方法中的文檔注釋。詳細(xì)見PHP官網(wǎng) PHP反射簡介
Reflection能干什么
在上面講到的,可以使用反射來獲取一個(gè)類的所有屬性以及方法還有注釋文檔,甚至可以獲取類屬性和方法的訪問權(quán)限[protected/private],這些特性使得PHP的使用靈活性得到非常大的提高。例如:
– Laravel 框架的所謂優(yōu)雅所在,即容器、依賴注入、IOC 控制反轉(zhuǎn)就是依靠這些特性實(shí)現(xiàn)的
– Hyperf 框架的注解路由也是根據(jù)反射獲得注釋來實(shí)現(xiàn)的
– 生成文檔 因?yàn)榉瓷淇梢垣@取類屬性和方法的訪問權(quán)限,可以掃描整個(gè)項(xiàng)目的所有文件再使用反射來生成文檔
– 測試驅(qū)動開發(fā) 利用反射獲取該類的所有方法的特性,進(jìn)行測試驅(qū)動開發(fā)
– 開發(fā)插件 利用反射獲取類的內(nèi)部結(jié)構(gòu)的特性,實(shí)現(xiàn) Hook 功能,例如框架插件的實(shí)現(xiàn)
Reflection的優(yōu)缺點(diǎn)
優(yōu)點(diǎn) 反射提供了對類的反解析,從而相比原本面向?qū)ο蟮木幊谭绞将@得了極高的靈活性,以及合理的使用能夠讓代碼看起來更加優(yōu)雅以及簡潔。原本在面向?qū)ο蟮木幊谭绞街?,使用一個(gè)類的實(shí)例需要先 new 出一個(gè)對象再使用方法,但是使用了反射機(jī)制,只需要提供一個(gè)該類的方法然后使用反射機(jī)制即可使用該對象或者方法。Laravel 框架正是使用了大量的反射才獲得了優(yōu)雅的美譽(yù),Swoole 的 Hyperf 框架的注解路由的實(shí)現(xiàn)也是使用了反射
缺點(diǎn) 同時(shí),由于反射是類實(shí)例化的反過程,破壞了面向?qū)ο蟮姆庋b性,直接將類的整個(gè)內(nèi)部結(jié)構(gòu)暴露,這就導(dǎo)致了反射一旦濫用,代碼將難于管理,整個(gè)項(xiàng)目將非?;靵y,甚至導(dǎo)致業(yè)務(wù)執(zhí)行錯(cuò)亂。尤其在大項(xiàng)目幾十人的團(tuán)隊(duì)中,試想一下,原本的面向?qū)ο?,只告訴什么可以用,什么不可以用,CTO寫好了底層代碼,其他人繼承后然后使用就行,內(nèi)部結(jié)構(gòu)啥的其他人都不知道。一旦用上了反射,如果有一個(gè)程序員不小心將原本是 protected 或者是 private 的屬性或者方法設(shè)置成了可以訪問,其他程序員在不知情的情況調(diào)用了本該隱藏的數(shù)據(jù)或者方法,那將導(dǎo)致不可預(yù)測的災(zāi)難【見下面示例代碼】
其次,由于反射的靈活性極高,這導(dǎo)致了無法在 IDE 中通過直接直接點(diǎn)擊代碼溯源,對于新手真的是很蛋疼,Laravel 和Hyperf 都是如此
在下面的代碼中,反射的機(jī)制直接將 private 方法設(shè)置成外部可訪問
#Example: setAccessible(true); echo $method->invoke(new Foo); // echos "7" ?>
工廠設(shè)計(jì)模式
三種工廠設(shè)計(jì)模式 [簡單工廠模式] [工廠模式] [抽象工廠模式]
簡單工廠模式 又稱為靜態(tài)工廠方法模式。簡單的說,就是創(chuàng)建對象的方式是通過一個(gè)靜態(tài)方法來實(shí)現(xiàn)的。在簡單工廠模式中,根據(jù)傳遞的參數(shù)來返回不同的類的實(shí)例
在PHP中在簡單工廠模式中,有一個(gè)抽象的產(chǎn)品類【即abstract class Calculate】,這個(gè)抽象類可以是接口/抽象類/普通類。這個(gè)抽象的產(chǎn)品類可以派生出多個(gè)具體的產(chǎn)品類【即class CalculateAdd以及class CalculateSub】。最后再由一個(gè)具體的工廠類【即class CalculateFactory】來獲取所需要的產(chǎn)品類的實(shí)例
代碼實(shí)現(xiàn)
1) 抽象產(chǎn)品生產(chǎn)類:運(yùn)算抽象類
//生產(chǎn)抽象類
abstract class Calculate{
//數(shù)字A
protected $number_a = null;
//數(shù)字B
protected $number_b = null;
//設(shè)置數(shù)字A
public function setNumberA( $number ){
$this->number_a = $number;
}
//設(shè)置數(shù)字B
public function setNumberB( $number ){
$this->number_b = $number;
}
//獲取數(shù)字A
public function getNumberA(){
return $this->number_a;
}
//獲取數(shù)字B
public function getNumberB(){
return $this->number_b;
}
//獲取計(jì)算結(jié)果【獲取生產(chǎn)出的產(chǎn)品】
public function getResult(){
return null;
}
}
2) 具體產(chǎn)品生產(chǎn)類:加法運(yùn)算 / 減法運(yùn)算 等等
//加法運(yùn)算
class CalculateAdd extends Calculate{
//獲取運(yùn)算結(jié)果【獲取具體的產(chǎn)品】
public function getResult(){
return $this->number_a + $this->number_b;
}
}
//減法運(yùn)算
class CalculateSub extends Calculate{
//獲取運(yùn)算結(jié)果【獲取具體的產(chǎn)品】
public function getResult(){
return $this->number_a - $this->number_b;
}
}
//乘法 / 除法 等等其他運(yùn)算【其他產(chǎn)品】
3) 工廠:工廠類。即用一個(gè)單獨(dú)的類來創(chuàng)造實(shí)例化的過程,這個(gè)類就是工廠。也就是 簡單工廠模式
在 php 中,實(shí)現(xiàn)的方式其實(shí)就一個(gè) switch 函數(shù)或者是 php8 新出的 match 函數(shù)來實(shí)例化所需要的產(chǎn)品生產(chǎn)類
//根據(jù)運(yùn)算不同實(shí)例化不同的對象
//【也就是根據(jù)所需產(chǎn)品,實(shí)例化對應(yīng)的產(chǎn)品類進(jìn)行生產(chǎn)】
//對應(yīng)的實(shí)現(xiàn)其實(shí)就是一個(gè)switch或者php8函數(shù)新出的match函數(shù)
//下面用最新的match函數(shù)做演示
class CalculateFactory{
public static function setCalculate( $type = null ){
return match( $type ){
'add' => (function(){
return new CalculateAdd();
})(),
'sub' => (function(){
return new CalculateSub();
})(),
default => null;
};
}
}
//具體使用
$calculate = CalculateFactory::setCalculate('add');
$calculate->setNumberA = 1;
$calculate->setNumberB = 2;
//計(jì)算
echo $calculate->getResult;//echo 3
總結(jié):
簡單工廠模式其實(shí)就是創(chuàng)建一個(gè)基類【abstract】,該類存放所有具體生產(chǎn)產(chǎn)品類的共用的代碼,但是沒有執(zhí)行過程,然后具體生產(chǎn)產(chǎn)品的類全部繼承基類再實(shí)現(xiàn)各自的生產(chǎn)過程。最后創(chuàng)建一個(gè)工廠類,該類用來根據(jù)傳入的參數(shù)來獲取所需的生產(chǎn)類
工廠方法模式 又稱為工廠模式,屬于創(chuàng)造型模式。在工廠模式中,工廠類的父類只負(fù)責(zé)定義公共接口,并不執(zhí)行實(shí)際的生產(chǎn)動作。實(shí)際的生產(chǎn)動作則交給工廠的子類來完成。這樣做將類的的實(shí)例化延遲到了工廠的子類,通過工廠的子類來完成實(shí)例化具體的產(chǎn)品,也就是生產(chǎn)
在工廠模式中,跟簡單工廠模式不一樣的是,有一個(gè)抽象的工廠類【即interface CalculateFactory】,可以是接口/抽象類,這個(gè)抽象的工廠類可以派生出多個(gè)具體的工廠類【即FactoryAdd以及FactorySub】
代碼實(shí)現(xiàn)【以下代碼需要用到上面的生產(chǎn)抽象類】
以下代碼需要用到上面的生產(chǎn)抽象類:abstract class Calculate
以及具體的生產(chǎn)類,即:CalculateAdd 以及 CalculateSub。下面不再重復(fù)實(shí)現(xiàn)
interface CalculateFactory{
public function CreateCalculate();
}
class FactoryAdd implements CalculateFactory{
public function CreateCalculate(){
return new CalculateAdd();
}
}
class FactorySub implements CalculateFactory{
public function CreateCalculate(){
return new CalculateSub();
}
}
//具體使用
//創(chuàng)建工廠實(shí)例
$calculateFactory = new FactoryAdd();
$add = $calculateFactory->CreateCalculate();
$add->setNumberA( 1 );
$add->setNumberB( 2 );
//計(jì)算
echo $add->getResult();//echo 3
總結(jié):
工廠模式相比于簡單工廠模式的區(qū)別在于,在簡單工廠模式中,只有一個(gè)工廠來生產(chǎn)對應(yīng)的生產(chǎn)對象【即CalculateFactory】。而在工廠模式中,每一個(gè)生產(chǎn)產(chǎn)對象都由自己的工廠來生產(chǎn),并且這些工廠都繼承自同一個(gè)接口【即 interface CalculateFactory】
抽象工廠模式 抽象工廠模式提供創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而且無需指定它們具體的類。這么理解很抽象。通俗一點(diǎn)的解釋就是,相比于上面的工廠模式來講,抽象工廠模式在每個(gè)不同的工廠之上又有一個(gè)超級工廠,這個(gè)超級工廠是抽象的接口【interface】,用來生產(chǎn)具體的工廠
在抽象工廠模式中,有多個(gè)抽象的產(chǎn)品類【即abstract class Phone以及abstract class Android】,可以是接口/抽象類/普通類,每個(gè)抽象產(chǎn)品類可以派生出多個(gè)具體產(chǎn)品類【即class IPhone / class MiPhone 以及 class IOS / class Android】。一個(gè)抽象的工廠類【即interface AbstractFactory】可以派生出多個(gè)具體的工廠類【即class iPhoneFactory以及class MiFactory】,且每個(gè)具體的工廠類可以創(chuàng)建多個(gè)產(chǎn)品類的實(shí)例【即都有createPhone和createSystem】
代碼實(shí)現(xiàn)
//抽象的產(chǎn)品類
abstract class Phone{}
abstract class System{}
//具體的產(chǎn)品類
class IPhone extends Phone{}
class MiPhone extends Phone{}
//具體的產(chǎn)品類
class IOS extends System{}
class Android extends System{}
//超級工廠
interface AbstractFactory{
public function createPhone();
public function createSystem();
}
//具體的蘋果工廠
class iPhoneFactory implements AbstractFactory{
//生產(chǎn)蘋果手機(jī)
public function createPhone(){
return new IPhone();
}
//生產(chǎn)蘋果系統(tǒng)
public function createSystem(){
return new IOS();
}
}
//具體的小米工廠
class MiFactory implements AbstractFactory{
//生產(chǎn)小米手機(jī)
public function createPhone(){
return new MiPhone();
}
//生產(chǎn)安卓系統(tǒng)
public function createSystem(){
return new Android();
}
}
總結(jié):
抽象工廠模式相比于工廠模式,抽象工廠模式提供了一個(gè)接口用來規(guī)定所需要生產(chǎn)的產(chǎn)品。每個(gè)繼承于該接口的工廠都能按照指定的模式進(jìn)行生產(chǎn)【代碼中的AbstarctFactory】
以上三種工廠模式,最終都是為了將重復(fù)的代碼提取出來,并且按照特定的需求場景歸納好,進(jìn)行解耦和復(fù)用,以便在需要的場景中直接使用
三種模式的概括為:
簡單工廠:
一個(gè)抽象產(chǎn)品類(可以是:接口,抽象類,普通類),可以派生出多個(gè)具體產(chǎn)品類
單獨(dú)一個(gè)具體的工廠類
每個(gè)具體工廠類只能創(chuàng)建一個(gè)具體產(chǎn)品類的實(shí)例
工廠模式:
一個(gè)抽象產(chǎn)品類(可以是:接口,抽象類,普通類),可以派生出多個(gè)具體產(chǎn)品類
一個(gè)抽象工廠類(可以是:接口,抽象類),可以派生出多個(gè)具體工廠類
每個(gè)具體工廠類只能創(chuàng)建一個(gè)具體產(chǎn)品類的實(shí)例
抽象工廠:
多個(gè)抽象產(chǎn)品類(可以是:接口,抽象類,普通類),每個(gè)抽象產(chǎn)品類可以派生出多個(gè)具體產(chǎn)品類
一個(gè)抽象工廠類(可以是:接口,抽象類),可以派生出多個(gè)具體工廠類
每個(gè)具體工廠類可以創(chuàng)建多個(gè)具體產(chǎn)品類的實(shí)例
三個(gè)模式之間的區(qū)別:
簡單工廠模式只有一個(gè)抽象產(chǎn)品類,只有一個(gè)具體的工廠類
工廠方法模式只有一個(gè)抽象產(chǎn)品類,而抽象工廠模式有多個(gè)抽象產(chǎn)品類
工廠方法模式的具體工廠類只能創(chuàng)建一個(gè)具體產(chǎn)品類的實(shí)例,而抽象工廠模式可以創(chuàng)建多個(gè)具體產(chǎn)品類的實(shí)例
工廠模式與反射的結(jié)合使用
可以利用反射的特性來實(shí)現(xiàn)工廠模式的生產(chǎn)過程,結(jié)合Laravel-admin進(jìn)行舉例
先看下以下的代碼,需求背景:需要根據(jù)角色不同顯示不同的權(quán)限按鈕
inRoles([AdminUserModel::getAssignmentRole()])) {
$grid->disableBatchActions();
$grid->disableEditButton();
$grid->disableCreateButton();
$grid->disableDeleteButton();
} elseif (Admin::user()->inRoles([AdminUserModel::getEvaluatorRole()])) {
$grid->disableBatchActions();
$grid->disableEditButton();
$grid->disableCreateButton();
$grid->disableDeleteButton();
$grid->actions(function (Grid\Displayers\Actions $actions) {
$actions->append(new ConfirmCloseTaskAction());
});
} else {
$grid->disableCreateButton();
$grid->disableDeleteButton();
$grid->disableEditButton();
$grid->disableBatchActions();
$grid->disableViewButton();
$grid->disableActions();
}
}
}
以上的代碼很明顯一看就顯得很臃腫。且隨著業(yè)務(wù)的增加【即Controller的增加】以及角色的增加,需要寫更多重復(fù)的判斷以及重復(fù)的代碼
解決思路:
不同的角色需要擁有的不同的權(quán)限,每個(gè)角色都可以用一個(gè)固定的方法來設(shè)置權(quán)限,這個(gè)固定的方法可以為不同的角色設(shè)置權(quán)限。這些條件剛好滿足工廠模式的使用場景:即:
抽象出一個(gè)產(chǎn)品類來派生出多個(gè)角色的權(quán)限產(chǎn)品類
抽象出一個(gè)工廠類來派生出多個(gè)具體的工廠類,這些工廠類表現(xiàn)為對應(yīng)要使用權(quán)限按鈕的場景
每個(gè)具體工廠【使用權(quán)限按鈕的場景】可以創(chuàng)建多個(gè)具體產(chǎn)品類【即實(shí)例化多個(gè)角色的權(quán)限產(chǎn)品】
代碼如下【在下面的代碼中,將使用反射來代替工廠的生產(chǎn)】
1) 抽象出一個(gè)產(chǎn)品類來派生出多個(gè)角色的權(quán)限產(chǎn)品類
2,3) 2,3兩個(gè)步驟包含在一起。抽象出一個(gè)工廠類來派生出多個(gè)具體的工廠類,這些工廠類表現(xiàn)為對應(yīng)要使用權(quán)限按鈕的場景。其中,setRoleAction方法使用反射來直接生產(chǎn),也就是替代了每個(gè)具體工廠類創(chuàng)建實(shí)例的過程
inRoles([$role]); } /** * 調(diào)用對應(yīng)的方法 * [該方法其實(shí)就是工廠模式中的工廠,專門來生產(chǎn)的] * [多個(gè)工廠對應(yīng)的就是各個(gè)需要用到Action權(quán)限的Controller控制器] * [每個(gè)Controller控制器來生產(chǎn)自己的Action權(quán)限] * [這個(gè)生產(chǎn)是通過反射來實(shí)現(xiàn)] * * @param Grid $grid * @param string $role * @param string $class * @throws \ReflectionException */ protected static function setRoleAction(Grid $grid, string $role, string $class) { $r = new \ReflectionClass($class); $methodName = $role . 'Action'; if (!$r->hasMethod($methodName)) throw new \Exception('Method Not Found [ method : ' . $methodName . ' ] '); $method = $r->getMethod($methodName); $method->invoke($r->newInstance(), $grid); } }根據(jù)以上的反射來實(shí)現(xiàn)實(shí)例化的過程,上面的TaskController的權(quán)限可以簡化成下面的代碼:
showActions(); $grid->showViewButton(); } //在TaskController下有需要使用權(quán)限按鈕的角色 //財(cái)務(wù)角色 public function financeAction(Grid $grid) { $grid->showActions(); $grid->showViewButton(); } //在TaskController下有需要使用權(quán)限按鈕的角色 //業(yè)務(wù)員角色 public function salesmanAction(Grid $grid) { } //....其他角色 }經(jīng)過使用設(shè)計(jì)模式封裝后,上面TaskController中控制權(quán)限的代碼直接優(yōu)化成如下:【優(yōu)雅了不少~】
inRoles([AdminUserModel::getAssignmentRole()])) { $grid->disableBatchActions(); $grid->disableEditButton(); $grid->disableCreateButton(); $grid->disableDeleteButton(); } elseif (Admin::user()->inRoles([AdminUserModel::getEvaluatorRole()])) { $grid->disableBatchActions(); $grid->disableEditButton(); $grid->disableCreateButton(); $grid->disableDeleteButton(); $grid->actions(function (Grid\Displayers\Actions $actions) { $actions->append(new ConfirmCloseTaskAction()); }); } else { $grid->disableCreateButton(); $grid->disableDeleteButton(); $grid->disableEditButton(); $grid->disableBatchActions(); $grid->disableViewButton(); $grid->disableActions(); } */ } }總結(jié):
設(shè)計(jì)模式以及反射通常在寫框架的時(shí)候用的比較多。但是在項(xiàng)目中,適當(dāng)?shù)氖褂迷O(shè)計(jì)模式以及反射,能夠讓代碼更加健壯以及可擴(kuò)展,也很優(yōu)雅~
本文題目:PHP高級特性:反射與工廠設(shè)計(jì)模式如何結(jié)合使用
網(wǎng)站地址:http://m.fisionsoft.com.cn/article/dpoogds.html


咨詢
建站咨詢
