PHP の高度な機能 - リフレクションとファクトリ デザイン パターンの組み合わせ [Laravel-Admin コード例で解説]
リフレクションを使用して、特定のファクトリ クラスを作成せずにファクトリ パターンの生成を実装する
Reflection [Reflection]
Reflection とは
反省、つまり反省。リフレクションは、オブジェクト指向プログラミングに内省機能を提供します。この理解は少し概念的すぎますが、平たく言えば、イベントの結果に基づいて原因を見つけることができることを意味します。プログラミングでは、インスタンス化されたオブジェクトに基づいて、そのオブジェクトが属するクラスとそのクラスが持つすべてのプロパティとメソッドを見つけることができ、ドキュメントのコメントを読むこともできます。この逆チェックのプロセスはリフレクションと呼ばれます [推奨学習:
PHP ビデオ チュートリアル]PHP は完全なリフレクション API を提供し、イントロスペクション クラス、インターフェイス、関数、メソッド、および拡張機能を提供します。さらに、Reflection API は、関数、クラス、メソッドからドキュメント コメントを抽出するメソッドを提供します。詳細については、PHP 公式 Web サイトを参照してください。 PHP リフレクションの概要
リフレクションでできること前述したように、リフレクションを使用すると、オブジェクトのすべてのプロパティとメソッドを取得できます。クラスやアノテーションドキュメントだけでなく、クラスの属性やメソッドへのアクセス権限[プロテクト/プライベート]を取得することもでき、これらの機能により、PHP の使用の柔軟性が大幅に向上します。例:
– Laravel フレームワークのいわゆるエレガンス、つまりコンテナ、依存関係の注入、IOC による制御の反転は、これらの機能に依存することで実現されます
– アノテーションのルーティングHyperf フレームワークの実装もリフレクションに基づいてアノテーションを取得します。 実装
– ドキュメントの生成 リフレクションはクラスの属性とメソッドへのアクセスを取得できるため、プロジェクト全体のすべてのファイルをスキャンし、リフレクションを使用してドキュメントを生成できます
– テスト駆動開発では、リフレクションを使用してクラスのすべてのメソッドの特性を取得し、テスト駆動開発を実施します。
-リフレクションを使用してクラスの内部構造の特性を取得するプラグインを開発します。
#Reflection の長所と短所
長所 Reflection はクラスのアンチ分析を提供するため、非常に優れた効果を発揮します。元のオブジェクト指向プログラミング手法と比較して柔軟性が高く、適切に使用することでコードをよりエレガントで簡潔に見せることができます。本来、オブジェクト指向プログラミングでは、クラスのインスタンスを使用するには、まずオブジェクトを新規作成してからメソッドを使用する必要がありますが、リフレクション機構を使用すると、クラスのメソッドを提供してからメソッドを使用するだけで済みます。オブジェクトまたはメソッドを使用するためのリフレクション メカニズム。 Laravel フレームワークは、優雅さの評判を得るためにリフレクションを多用しています。Swoole の Hyperf フレームワークのアノテーション ルーティングの実装でもリフレクションが使用されています。
短所 同時に、リフレクションはクラスのインスタンス化の逆のプロセスであるため、オブジェクト指向のカプセル化はクラスの内部構造全体を直接公開するため、リフレクションが乱用されるとコードの管理が難しくなり、プロジェクト全体が非常に混乱し、ビジネスの実行の中断につながることもあります。特に大規模なプロジェクトに数十人が参加するチームでは、元のオブジェクト指向システムでは何が使用可能で何が使用できないかだけがわかると想像してください。CTO が基礎となるコードを記述し、他の人がそれを継承して使用できます。 . 内部構造などは誰も知りません。リフレクションを使用すると、プログラマが、もともと保護されていた、またはプライベートだったプロパティやメソッドを誤ってアクセスできるように設定し、他のプログラマがそれを知らずに非表示にする必要があるデータやメソッドを呼び出した場合、予期せぬ災害につながる可能性があります [サンプル コードを参照]次に、リフレクションの柔軟性が非常に高いため、IDE 内で直接クリックしてコードのソースをトレースすることが不可能になり、初心者にとっては非常に面倒です。これは両方に当てはまります。 Laravel と Hyperf次のコードでは、リフレクション メカニズムがプライベート メソッドを外部からアクセスできるように直接設定します#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" ?>
ファクトリ デザイン パターン
3 つのファクトリ設計パターン [単純なファクトリ パターン] [ファクトリ パターン] [抽象的なファクトリ パターン]
単純なファクトリ パターンは、静的ファクトリ メソッド パターンとも呼ばれます。簡単に言えば、オブジェクトを作成する方法は静的メソッドを使用することです。単純なファクトリ パターンでは、渡されたパラメータに従ってさまざまなクラスのインスタンスが返されます。PHP では、単純なファクトリ パターンでは、抽象製品クラス [つまり、抽象クラス Calculate] が存在します。この抽象クラスは次のようになります。インターフェース/抽象クラス/通常クラス。この抽象製品クラスは、複数の特定の製品クラス [つまり、クラス CalculateAdd およびクラス CalculateSub] を派生できます。最後に、特定のファクトリ クラス [つまり、クラス 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; } }
//加法运算 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(); } */ } }
总结:
设计模式以及反射通常在写框架的时候用的比较多。但是在项目中,适当的使用设计模式以及反射,能够让代码更加健壮以及可扩展,也很优雅~
原文地址:http://janrs.com/?p=833
以上がPHP の高度な機能: リフレクションとファクトリ デザイン パターンを併用する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。