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 框架正是应用了大量的反射才取得了优雅的美誉,SwooleHyperf 框架的注解路由的实现也是应用了反射

毛病 同时,因为反射是类实例化的反过程,毁坏了面向对象的封装性,间接将类的整个内部结构裸露,这就导致了反射一旦滥用,代码将难于治理,整个我的项目将十分凌乱,甚至导致业务执行错乱。尤其在大我的项目几十人的团队中,试想一下,本来的面向对象,只通知什么能够用,什么不能够用,CTO写好了底层代码,其他人继承后而后应用就行,内部结构啥的其他人都不晓得。一旦用上了反射,如果有一个程序员不小心将本来是 protected 或者是 private 的属性或者办法设置成了能够拜访,其余程序员在不知情的状况调用了本该暗藏的数据或者办法,那将导致不可预测的劫难【见上面示例代码】

其次,因为反射的灵活性极高,这导致了无奈在 IDE 中通过间接间接点击代码溯源,对于老手真的是很蛋疼,LaravelHyperf 都是如此

在上面的代码中,反射的机制间接将 private 办法设置成内部可拜访
#Example:<?phpclass 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】,且每个具体的工厂类能够创立多个产品类的实例【即都有createPhonecreateSystem

代码实现

//形象的产品类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) 形象出一个产品类来派生出多个角色的权限产品类
<?phpnamespace 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办法应用反射来间接生产,也就是代替了每个具体工厂类创立实例的过程
<?phpnamespace 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的权限能够简化成上面的代码:

<?phpnamespace 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();            }            */    }}

总结:设计模式以及反射通常在写框架的时候用的比拟多。然而在我的项目中,适当的应用设计模式以及反射,可能让代码更加强壮以及可扩大,也很优雅~

欢送来我的博客逛一逛 杨建勇的集体博客http://janrs.com