资讯专栏INFORMATION COLUMN

PHP高级特性-反射Reflection以及Factory工厂设计模式的结合使用[代码实例]

番茄西红柿 / 3196人阅读

摘要:反射提供给面向对象编程可以自省的能力,即反射。在简单工厂模式中,根据传递的参数来返回不同的类的实例简单工厂模式又称为静态工厂方法模式。也就是简单工厂模式工厂工厂类。

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:setAccessible(true);echo $method->invoke(new Foo);// echos "7"?>

工厂设计模式

三种工厂设计模式 [简单工厂模式] [工厂模式] [抽象工厂模式]

简单工厂模式 又称为静态工厂方法模式。简单的说,就是创建对象的方式是通过一个静态方法来实现的。在简单工厂模式中,根据传递的参数来返回不同的类的实例

PHP中在简单工厂模式中,有一个抽象的产品类【即abstract class Calculate】,这个抽象类可以是接口/抽象类/普通类。这个抽象的产品类可以派生出多个具体的产品类【即class CalculateAdd以及class CalculateSub】。最后再由一个具体的工厂类【即class CalculateFactory】来获取所需要的产品类的实例

JARNS.COM - 工厂模式[简单工厂UML图] JARNS.COM - 工厂模式[简单工厂UML图]

代码实现

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

JARNS.COM - 工厂模式[工厂UML图] 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】,且每个具体的工厂类可以创建多个产品类的实例【即都有createPhonecreateSystem

JARNS.COM - 抽象工厂模式[工厂UML图] 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进行举例

先看下以下的代码,需求背景:需要根据角色不同显示不同的权限按钮

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) 抽象出一个产品类来派生出多个角色的权限产品类
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的权限可以简化成下面的代码:

showActions();        $grid->showViewButton();    }	//在TaskController下有需要使用权限按钮的角色	//财务角色    public function financeAction(Grid $grid)    {		$grid->showActions();        $grid->showViewButton();    }	//在TaskController下有需要使用权限按钮的角色	//业务员角色    public function salesmanAction(Grid $grid)    {    }		//....其他角色}

经过使用设计模式封装后,上面TaskController中控制权限的代码直接优化成如下:【优雅了不少~

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/123756.html

相关文章

  • php实现依赖注入(DI)和控制反转(IOC)

    摘要:工厂模式,依赖转移当然,实现控制反转的方法有几种。其实我们稍微改造一下这个类,你就明白,工厂类的真正意义和价值了。虽然如此,工厂模式依旧十分优秀,并且适用于绝大多数情况。 此篇文章转载自laravel-china,chongyi的文章https://laravel-china.org/top...原文地址: http://www.insp.top/learn-lar... ,转载务必保...

    tomato 评论0 收藏0
  • 深入理解控制反转(IoC)和依赖注入(DI)

    摘要:本文一大半内容都是通过举例来让读者去理解什么是控制反转和依赖注入,通过理解这些概念,来更加深入。这种由外部负责其依赖需求的行为,我们可以称其为控制反转。工厂模式,依赖转移当然,实现控制反转的方法有几种。 容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描...

    HollisChuang 评论0 收藏0
  • EMF学习笔记(二)——使用EMF编程——开发元数据

    摘要:使用元数据包中包含了中每一个被建模类对应的接口。任何对象的元数据是使用的实现来表示的。加载模型的序列化形式是个在运行期间获取元数据的有效方法。反射提供一个反射式,可以检查对象的元数据以及一般地访问和操纵数据。 使用元数据   Java包org.eclipse.emf.ecore中包含了Ecore中每一个被建模类对应的接口。任何EMF对象的元数据是使用Ecore的实现(implement...

    Jiavan 评论0 收藏0
  • PHP高级特性】ArrayAccess 接口

    摘要:提供了个常用的预定义接口,实现某些特定的能力。是啥如官方文档所述,它提供像访问数组一样访问对象的能力的接口。它提供了个接口我们实现这个接口,依次对应数组的读取,设置,操作。用上了它,可以让一个类即可以支持对象引用,也支持数组引用。 php提供了6个常用的预定义接口,实现某些特定的能力。其中最最常用的就是 ArrayAccess 了,像 Laravel 这种流行的框架都用到了它。 Arr...

    mrli2016 评论0 收藏0
  • Reflection:Java反射机制应用场景

    近期在维护公司项目的时候遇到一个问题,因为实体类中的 set 方法涉及到了业务逻辑,因此在给对象赋值的过程中不能够使用 set 方法,为了实现功能,所以采用了反射的机制给对象属性赋值,借此机会也了解了反射的一些具体用法和使用场景,分以下两点对反射进行分析: 反射的优势和劣势 反射的应用场景 反射的优势和劣势   个人理解,反射机制实际上就是上帝模式,如果说方法的调用是 Java 正确的打开方式...

    浠ラ箍 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<