利用反射来实现工厂模式的生产而无需创建特定的工厂类
转载请注明来源
[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
】来获取所需要的产品类的实例JARNS.COM - 工厂模式[简单工厂UML图]
//生产抽象类 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; } }
//加法运算 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; } } //乘法 / 除法 等等其他运算【其他产品】
简单工厂模式
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
】JARNS.COM - 工厂模式[工厂UML图]
以下代码需要用到上面的生产抽象类: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
】JARNS.COM - 抽象工厂模式[工厂UML图]
//抽象的产品类 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
的增加】以及角色的增加,需要写更多重复的判断以及重复的代码抽象出一个产品类来派生出多个角色的权限产品类
抽象出一个工厂类来派生出多个具体的工厂类,这些工厂类表现为对应要使用权限按钮的场景
每个具体工厂【使用权限按钮的场景】可以创建多个具体产品类【即实例化多个角色的权限产品】
<!--?php namespace App\GridActionFactory; use Dcat\Admin\Grid; /** * 工厂接口 */ interface GridActionInterface { //业务员角色的权限 function salesmanAction(Grid $grid); //分配员角色的权限 function assignmentAction(Grid $grid); //财务角色的权限 function financeAction(Grid $grid); //....其他角色的权限 }
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); } }
<!--?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(); } */ } }