PHP 高级个性 - 反射以及工厂设计模式的联合应用 [联合 Laravel-Admin 代码实例解说]
利用反射来实现工厂模式的生产而无需创立特定的工厂类
本文地址 http://janrs.com/?p=833 转载无需通过作者自己受权
转载请注明起源
反射[Relfection]
JANRS.COM – PHP Reflection 反射
什么是Reflection
Reflection
,即反射。反射提供给面向对象编程能够自省的能力
这么了解有点太过于概念化,艰深地讲,就是能依据事件的后果反查出起因。在编程中,能够依据一个被实例化的对象,反查出这个对象属于的类以及该类领有所有属性以及办法,甚至能够读取文档正文。这个反查的过程就叫做反射
PHP
提供了残缺的反射 API
,提供了内省类、接口、函数、办法和扩大的能力。此外,反射 API
提供了办法来取出函数、类和办法中的文档正文。具体见PHP 官网
PHP 反射简介
Reflection
能干什么
在下面讲到的,能够应用反射来获取一个类的所有属性以及办法还有正文文档,甚至能够获取类属性和办法的拜访权限[protected/private]
,这些个性使得 PHP 的应用灵活性失去十分大的进步。例如:
– Laravel
框架的所谓优雅所在,即容器、依赖注入、IOC
管制反转就是依附这些个性实现的
– Hyperf
框架的注解路由也是依据反射取得正文来实现的
– 生成文档
因为反射能够获取类属性和办法的拜访权限,能够扫描整个我的项目的所有文件再应用反射来生成文档
– 测试驱动开发
利用反射获取该类的所有办法的个性,进行测试驱动开发
– 开发插件
利用反射获取类的内部结构的个性,实现 Hook
性能,例如框架插件的实现
Reflection
的优缺点
长处
反射提供了对类的反解析,从而相比本来面向对象的编程形式取得了极高的灵活性,以及正当的应用可能让代码看起来更加优雅以及简洁。本来在面向对象的编程形式中,应用一个类的实例须要先 new
出一个对象再应用办法,然而应用了反射机制,只须要提供一个该类的办法而后应用反射机制即可应用该对象或者办法。Laravel
框架正是应用了大量的反射才取得了优雅的美誉,Swoole
的 Hyperf
框架的注解路由的实现也是应用了反射
毛病
同时,因为反射是类实例化的反过程,毁坏了面向对象的封装性,间接将类的整个内部结构裸露,这就导致了反射一旦滥用,代码将难于治理,整个我的项目将十分凌乱,甚至导致业务执行错乱。尤其在大我的项目几十人的团队中,试想一下,本来的面向对象,只通知什么能够用,什么不能够用,CTO 写好了底层代码,其他人继承后而后应用就行,内部结构啥的其他人都不晓得。一旦用上了反射,如果有一个程序员不小心将本来是 protected
或者是 private
的属性或者办法设置成了能够拜访,其余程序员在不知情的状况调用了本该暗藏的数据或者办法,那将导致不可预测的劫难【见上面示例代码】
其次,因为反射的灵活性极高,这导致了无奈在 IDE
中通过间接间接点击代码溯源,对于老手真的是很蛋疼,Laravel
和Hyperf
都是如此
在上面的代码中,反射的机制间接将 private
办法设置成内部可拜访
#Example:
<?php
class Foo {private function myPrivateMethod() {return 7;}
}
$method = new ReflectionMethod('Foo', 'myPrivateMethod');
// 该反射性能间接将本来是 private 权限的办法设置成可拜访
$method->setAccessible(true);
echo $method->invoke(new Foo);
// echos "7"
?>
工厂设计模式
三种工厂设计模式 [简略工厂模式] [工厂模式] [形象工厂模式]
简略工厂模式
又称为动态工厂办法模式。简略的说,就是创建对象的形式是通过一个静态方法来实现的。在简略工厂模式中,依据传递的参数来返回不同的类的实例
在 PHP
中在简略工厂模式中,有一个形象的产品类【即 abstract class Calculate
】,这个抽象类能够是 接口 / 抽象类 / 一般类
。这个形象的产品类能够派生出多个具体的产品类【即class CalculateAdd
以及class CalculateSub
】。最初再由一个具体的工厂类【即class CalculateFactory
】来获取所须要的产品类的实例
代码实现
1) 形象产品生产类:运算抽象类
// 生产抽象类
abstract class Calculate{
// 数字 A
protected $number_a = null;
// 数字 B
protected $number_b = null;
// 设置数字 A
public function setNumberA($number){$this->number_a = $number;}
// 设置数字 B
public function setNumberB($number){$this->number_b = $number;}
// 获取数字 A
public function getNumberA(){return $this->number_a;}
// 获取数字 B
public function getNumberB(){return $this->number_b;}
// 获取计算结果【获取生产出的产品】public function getResult(){return null;}
}
2) 具体产品生产类:加法运算 / 减法运算 等等
// 加法运算
class CalculateAdd extends Calculate{
// 获取运算后果【获取具体的产品】public function getResult(){return $this->number_a + $this->number_b;}
}
// 减法运算
class CalculateSub extends Calculate{
// 获取运算后果【获取具体的产品】public function getResult(){return $this->number_a - $this->number_b;}
}
// 乘法 / 除法 等等其余运算【其余产品】
3) 工厂:工厂类。即用一个独自的类来发明实例化的过程,这个类就是工厂。也就是 简略工厂模式
在 php
中,实现的形式其实就一个 switch
函数或者是 php8
新出的 match
函数来实例化所须要的产品生产类
// 依据运算不同实例化不同的对象
//【也就是依据所需产品,实例化对应的产品类进行生产】// 对应的实现其实就是一个 switch 或者 php8 函数新出的 match 函数
// 上面用最新的 match 函数做演示
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;
// 计算
echo $calculate->getResult;//echo 3
总结
:简略工厂模式其实就是创立一个基类【abstract
】,该类寄存所有具体生产产品类的 共用的代码
,然而 没有执行过程
,而后具体生产产品的类全副继承基类再实现 各自的生产过程
。最初创立一个工厂类,该类用来依据传入的 参数
来获取所需的 生产类
工厂办法模式
又称为工厂模式,属于创造型模式。在工厂模式中,工厂类的父类只负责定义公共接口,并不执行理论的生产动作。理论的生产动作则交给工厂的子类来实现。这样做将类的的实例化提早到了工厂的子类,通过工厂的子类来实现实例化具体的产品,也就是生产
在工厂模式中,跟简略工厂模式不一样的是,有一个形象的工厂类【即 interface CalculateFactory
】,能够是 接口 / 抽象类
,这个形象的工厂类能够派生出多个具体的工厂类【即FactoryAdd
以及FactorySub
】
代码实现【以下代码须要用到下面的生产抽象类】
以下代码须要用到下面的生产抽象类:abstract class Calculate
以及具体的生产类,即:CalculateAdd
以及 CalculateSub
。上面不再反复实现
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();
}
}
// 具体应用
// 创立工厂实例
$calculateFactory = new FactoryAdd();
$add = $calculateFactory->CreateCalculate();
$add->setNumberA(1);
$add->setNumberB(2);
// 计算
echo $add->getResult();//echo 3
总结:工厂模式相比于简略工厂模式的区别在于,在简略工厂模式中,只有一个工厂
来生产对应的生产对象【即 CalculateFactory
】。而在工厂模式中,每一个生产产对象都由 本人的工厂
来生产,并且这些工厂都 继承
自同一个接口【即 interface CalculateFactory
】
形象工厂模式
形象工厂模式提供创立一系列相干或相互依赖对象的接口,而且无需指定它们具体的类。这么了解很形象。艰深一点的解释就是,相比于下面的工厂模式来讲,形象工厂模式在每个不同的工厂之上又有一个超级工厂,这个超级工厂是形象的接口【interface
】,用来生产具体的工厂
在形象工厂模式中,有多个形象的产品类【即 abstract class Phone
以及 abstract class Android
】,能够是 接口 / 抽象类 / 一般类
,每个形象产品类能够派生出多个具体产品类【即class IPhone
/ class MiPhone
以及 class IOS
/ class Android
】。一个形象的工厂类【即interface AbstractFactory
】能够派生出多个具体的工厂类【即class iPhoneFactory
以及 class MiFactory
】,且每个具体的工厂类能够创立多个产品类的实例【即都有createPhone
和createSystem
】
代码实现
// 形象的产品类
abstract class Phone{}
abstract class System{}
// 具体的产品类
class IPhone extends Phone{}
class MiPhone extends Phone{}
// 具体的产品类
class IOS extends System{}
class Android extends System{}
// 超级工厂
interface AbstractFactory{public function createPhone();
public function createSystem();}
// 具体的苹果工厂
class iPhoneFactory implements AbstractFactory{
// 生产苹果手机
public function createPhone(){return new IPhone();
}
// 生产苹果零碎
public function createSystem(){return new IOS();
}
}
// 具体的小米工厂
class MiFactory implements AbstractFactory{
// 生产小米手机
public function createPhone(){return new MiPhone();
}
// 生产安卓零碎
public function createSystem(){return new Android();
}
}
总结:形象工厂模式相比于工厂模式,形象工厂模式提供了一个接口用来规定所须要生产的产品。每个继承于该接口的工厂都能依照指定的模式进行生产【代码中的AbstarctFactory
】
以上三种工厂模式,最终都是为了将反复的代码提取进去,并且依照特定的需要场景演绎好,进行解耦和复用,以便在须要的场景中间接应用
三种模式的概括为:
简略工厂:
一个形象产品类(能够是:接口,抽象类,一般类),能够派生出多个具体产品类
独自一个具体的工厂类
-
每个具体工厂类只能创立一个具体产品类的实例
工厂模式:
一个形象产品类(能够是:接口,抽象类,一般类),能够派生出多个具体产品类
一个形象工厂类(能够是:接口,抽象类),能够派生出多个具体工厂类
-
每个具体工厂类只能创立一个具体产品类的实例
形象工厂:
多个形象产品类(能够是:接口,抽象类,一般类),每个形象产品类能够派生出多个具体产品类
一个形象工厂类(能够是:接口,抽象类),能够派生出多个具体工厂类
每个具体工厂类能够创立多个具体产品类的实例
三个模式之间的区别:
简略工厂模式只有一个形象产品类,只有一个具体的工厂类
工厂办法模式只有一个形象产品类,而形象工厂模式有多个形象产品类
工厂办法模式的具体工厂类只能创立一个具体产品类的实例,而形象工厂模式能够创立多个具体产品类的实例
工厂模式与反射的联合应用
能够利用反射的个性来实现工厂模式的生产过程,联合 Laravel-admin
进行举例
先看下以下的代码,需要背景:须要依据角色不同显示不同的权限按钮
<?php
class TaskController extends BaseController
{
use HasResourceActions;
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
//Grid Columns...
if (Admin::user()->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();}
}
}
以上的代码很显著一看就显得很臃肿。且随着业务的减少【即 Controller
的减少】以及角色的减少,须要写更多反复的判断以及反复的代码
解决思路:不同的角色须要领有的不同的权限,每个角色都能够用一个固定的办法来设置权限,这个固定的办法能够为不同的角色设置权限。这些条件刚好满足工厂模式的应用场景:即:
形象出一个产品类来派生出多个角色的权限产品类
形象出一个工厂类来派生出多个具体的工厂类,这些工厂类体现为对应要应用权限按钮的场景
每个具体工厂【应用权限按钮的场景】能够创立多个具体产品类【即实例化多个角色的权限产品】
代码如下【在上面的代码中,将应用反射来代替工厂的生产】
1) 形象出一个产品类来派生出多个角色的权限产品类
<?php
namespace App\GridActionFactory;
use Dcat\Admin\Grid;
/**
* 工厂接口
*/
interface GridActionInterface
{
// 业务员角色的权限
function salesmanAction(Grid $grid);
// 调配员角色的权限
function assignmentAction(Grid $grid);
// 财务角色的权限
function financeAction(Grid $grid);
//.... 其余角色的权限
}
2,3) 2,3 两个步骤蕴含在一起
。形象出一个工厂类来派生出多个具体的工厂类,这些工厂类体现为对应要应用权限按钮的场景。其中,setRoleAction
办法应用反射来间接生产,也就是代替了每个具体工厂类创立实例的过程
<?php
namespace App\GridActionFactory;
use Dcat\Admin\Admin;
use Dcat\Admin\Grid;
/**
* 设置 Action 权限抽象类
*/
abstract class GridActionAbstract
{
//
abstract public static function setAction(Grid $grid, string $role);
/**
* 过滤角色
*
* @param string $role
* @return bool
*/
protected static function isInRoles(string $role): bool
{return Admin::user()->inRoles([$role]);
}
/**
* 调用对应的办法
* [该办法其实就是工厂模式中的工厂,专门来生产的]
* [多个工厂对应的就是各个须要用到 Action 权限的 Controller 控制器]
* [每个 Controller 控制器来生产本人的 Action 权限]
* [这个生产是通过反射来实现]
*
* @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);
}
}
依据以上的反射来实现实例化的过程,下面的 TaskController
的权限能够简化成上面的代码:
<?php
namespace App\GridActionFactory;
use Dcat\Admin\Grid;
class TaskAction extends GridActionAbstract implements GridActionInterface
{
/**
* @param Grid $grid
* @param string $role
* @throws \ReflectionException
*/
public static function setAction(Grid $grid, string $role)
{if (!parent::isInRoles($role)) return;
// 通过调用父类的 setRoleAction 间接实现生产的过程
parent::setRoleAction($grid, $role, self::class);
}
// 在 TaskController 下有须要应用权限按钮的角色
// 调配员角色
public function assignmentAction(Grid $grid)
{
// 权限按钮
$grid->showActions();
$grid->showViewButton();}
// 在 TaskController 下有须要应用权限按钮的角色
// 财务角色
public function financeAction(Grid $grid)
{$grid->showActions();
$grid->showViewButton();}
// 在 TaskController 下有须要应用权限按钮的角色
// 业务员角色
public function salesmanAction(Grid $grid)
{ }
//.... 其余角色
}
通过应用设计模式封装后,下面 TaskController
中管制权限的代码间接优化成如下:【优雅了不少~
】
<?php
class TaskController extends BaseController
{
use HasResourceActions;
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
//Grid Columns...
// 财务角色按钮
TaskAction::setAction($grid, AdminUserModel::getFinanceRole());
// 调配员角色按钮
TaskAction::setAction($grid, AdminUserModel::getAssignmentRole());
//... 其余角色按钮
/*
if (Admin::user()->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();}
*/
}
}