代理模式(Proxy)和PHP的反射功能
本文包括以下内容:
- 代理模式概述
- 代理模式常规示例
- 使用PHP的反射功能实现多代理
正文
模式意图 :为其他对象提供一种代理以控制对这个对象的访问[GOF95]
代理模式是对象的结构模式,代理模式给某一个对象提供一个代理对象,并由此代理对象控制对原代理对象的引用。代理模式不应该让用户感觉到代理的存在,所以代理对象和原对象的对外的调用接口是一致的。
代理模式一般包括三个角色:
- 抽象主题角色(Subject):它的作用是统一接口。此角色定义了真实主题角色和代理主题角色共用的接口,这样就可以在使用真实主题角色的地方使用代理主题角色。
- 真实主题角色(RealSubject):隐藏在代理角色后面的真实对象。
- 代理主题角色(ProxySubject):它的作用是代理真实主题,在其内部保留了对真实主题角色的引用。它与真实主题角色都继承自抽象主题角色,保持接口的统一。它可以控制对真实主题的存取,并可能负责创建和删除真实对象。代理角色并不是简单的转发,通常在将调用传递给真实对象之前或之后执行某些操作,当然你也可以只是简单的转发。 与适配器模式相比:适配器模式是为了改变对象的接口,而代理模式并不能改变所代理对象的接口。
从以上三个角色我们可以得出一个简单的示例:
/** * 代理模式简单示例 2011-10-30 sz * @author phppan.p#gmail.com http://www.phppan.com * @package design pattern */ /** * 抽象主题角色 */ abstract class Subject { abstract public function action(); } /** * 真实主题角色 */ class RealSubject extends Subject { public function __construct() { } public function action() { echo "action method in RealSubject<br />\r\n"; } } /** * 代理主题角色 */ class ProxySubject extends Subject { private $_real_subject = NULL; public function __construct() { } public function action() { $this->_beforeAction(); if (is_null($this->_real_subject)) { $this->_real_subject = new RealSubject(); } $this->_real_subject->action(); $this->_afterAction(); } /** * 请求前的操作 */ private function _beforeAction() { echo "Before action in ProxySubject<br />\r\n"; } /** * 请求后的操作 */ private function _afterAction() { echo "After action in ProxySubject<br />\r\n"; } } /** * 客户端 */ class Client { public static function main() { $subject = new ProxySubject(); $subject->action(); } } Client::main(); ?>
以上的示例适用于代理模式为一个对象提供一个代理对象。当为多个对象提供代理对象时,是否我们需要创建多个代理对象呢? 如此按这种实现确实是,但是我们也可以将一个代理对象给多个对象用,或者可以作为一个代理工厂,此时一个对象会保留多个对象的引用, 并且在执行操作时需要判断所代理的是哪个对象,那么此时这种判断如何进行呢? 以多个instanceof判断对象的归属?但是这样,代理对象也必须知道真实对象的方法, 如果我们现在的需求是实现一个代理对象和真实对象的松耦合,但是在代理对象中,对于每个真实对象都有一个前置操作和后转操作, 也许我们可以使用反射,那么反射是什么呢?
反射一般来说是指在程序执行过程中获取程序相关的信息或者修改程序信息。 如获取类、方法、函数等的详细信息,或删除类方法定义。ruby的反射功能实现了类、对象、常量、变更等的获取和修改,但是在PHP中只有获取功能而无修改。 在PHP中我们可以使用PHP5以后的Reflection扩展, 此扩展是PHP的核心扩展,在PHP安装时就已经自动加载,它的作用是分析PHP程序, 导出或提取出关于类、方法、函数、属性、参数等的详细信息,包括注释。 在这里我们经常可以此扩展来实现对PHP程序内部关于类、方法等的信息检测,并做作出处理。
<?PHP /** * 使用反射实现代理工厂 2011-10-30 sz * @author phppan.p#gmail.com http://www.phppan.com * @package design pattern */ /** * 真实主题角色 A */ final class RealSubjectA { public function __construct() { } public function actionA() { echo "actionA method in RealSubject A <br />\r\n"; } } /** * 真实主题角色 B */ final class RealSubjectB { public function __construct() { } public function actionB() { echo "actionB method in RealSubject B <br />\r\n"; } } /** * 代理主题角色 */ final class ProxySubject { private $_real_subjects = NULL; public function __construct() { $this->_real_subjects = array(); } /** * 动态添加真实主题 * @param type $subject */ public function addSubject($subject) { $this->_real_subjects[] = $subject; } public function __call($name, $args) { foreach ($this->_real_subjects as $real_subject) { /* 使用反射获取类及方法相关信息 */ $reflection = new ReflectionClass($real_subject); /* 如果不存在此方法,下一元素 */ if (!$reflection->hasMethod($name)) { continue; } $method = $reflection->getMethod($name); /* 判断方法是否为公用方法并且是否不为抽象方法 */ if ($method && $method->isPublic() && !$method->isAbstract()) { $this->_beforeAction(); $method->invoke($real_subject, $args); $this->_afterAction(); break; } } } /** * 请求前的操作 */ private function _beforeAction() { echo "Before action in ProxySubject<br />\r\n"; } /** * 请求后的操作 */ private function _afterAction() { echo "After action in ProxySubject<br />\r\n"; } } /** * 客户端 */ class Client { public static function main() { $subject = new ProxySubject(); $subject->addSubject(new RealSubjectA()); $subject->addSubject(new RealSubjectB()); $subject->actionA(); $subject->actionB(); } } Client::main(); ?>
以上示例使用反射机制实现了对不同真实对象的代理,但是还存在一些问题:
- 对象的显式添加,不能对用户透明
- 真实对象的重复问题,如果存在多个相同的对象添加到对象列表中,如何处理?Flyweigh?