ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
**代理(Proxy)模式:** 为某对象提供一种代理以控制对该对象的访问。即客户端通过代理**间接地访问**该对象,从而限制、增强或修改该对象的一些特性。 常见的代理模式:远程代理、虚拟代理、保护代理、智能引用代理 **代理模式的主要优点有:** * 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用; * 代理对象可以扩展目标对象的功能; * 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度; **其主要缺点是:** * 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢; * 增加了系统的复杂度; ## **代理模式的应用场景** 前面分析了代理模式的结构与特点,现在来分析以下的应用场景。 * **远程代理**,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。 * **虚拟代理**,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。 * **安全代理**,这种方式通常用于控制不同种类客户对真实对象的访问权限。 * **智能指引**,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。 * **延迟加载**,指为了提高系统的性能,延迟对目标的加载。例如,[Hibernate](http://c.biancheng.net/hibernate/)中就存在属性的延迟加载和关联表的延时加载。 #### **模式的结构** 1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。 2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。 3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。 ![](https://img.kancloud.cn/20/98/2098786632a6bf2aa00b80d1da3570cd_672x355.png) ``` <?php class ProxyTest { public static function main() { $proxy=new Proxy(); $proxy->Request(); } } //抽象主题 interface Subject { public function Request(); } //真实主题 class RealSubject implements Subject { public function Request() { echo "访问真实主题方法..."; } } //代理 class Proxy implements Subject { private $realSubject=null; public function Request() { if ($this->realSubject==null) { $this->realSubject=new RealSubject(); } $this->preRequest(); $this->realSubject->Request(); $this->postRequest(); } public function preRequest() { echo "访问真实主题之前的预处理。"; } public function postRequest() { echo "访问真实主题之后的后续处理。"; } } ProxyTest::main(); ``` ``` //公司内部类 class User { public function getName() { return '张三'; } public function getType() { return '付费用户'; } } //代理接口定义,例如开放平台 interface UserInterface { function getName(); } //代理对象 class UserProxy implements UserInterface { protected $_user; function __construct() { $this->_user = new User(); } public function getName() { return $this->_user->getName(); } } //公司内部调用 $user = new User(); printf("user name:%sn", $user->getName()); printf("user type:%sn", $user->getType()); //公司外部调用(开放给用户的) $user = new UserProxy(); printf("user name:%sn", $user->getName()); printf("user type:%sn", $user->getType()); //不能访问,及时知道内部对象有这个方法 ``` ## 代理模式的扩展 在前面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点。 1. 真实主题与代理主题一一对应,增加真实主题也要增加代理。 2. 设计代理以前真实主题必须事先存在,不太灵活。采用动态代理模式可以解决以上问题,如[Spring](http://c.biancheng.net/spring/)AOP,其结构图如图 4 所示。 ![](https://img.kancloud.cn/6e/60/6e60cbe965d2d44df823d6d37176962b_698x628.png) 变相的动态代理 ``` //调用B的show方法时候去调用A的show方法 class A{ function show($param=20){ echo "classA的show方法,参数值为{$param}"; } } class B{ private $obj; function __construct(){ $this->obj = new A(); } function __call($name, $arguments) { $ref = new ReflectionClass($this->obj); //检查方法是否已定义 if ($ref->hasMethod($name)){ //获取一个类方法的 ReflectionMethod对象。相当于new ReflectionMethod('class', 'myMethod') $method = $ref->getMethod($name); if ($method->isPublic()&&!$method->isAbstract()&&count($arguments)<1){ if ($method->isStatic()){ //如果是静态方法,不需要传入调用的对象 $method->invoke(null); }else{ $method->invoke($this->obj); } }else{ if ($method->isStatic()){ //如果是静态方法,不需要传入调用的对象 $method->invokeArgs(null,$arguments); }else{ $method->invokeArgs($this->obj,$arguments); } } } } } $b=new B(); $b->show();//classA的show方法,参数值为20 $b->show(21);//classA的show方法,参数值为21 ``` **动态代理:** ``` //真实角色接口类 AbstractSubject interface IGamePlayer { // 登录游戏 public function login(String $user, String $password); // 杀怪 public function killBoss(); // 升级 public function upgrade(); } //真实角色类:RealSubject class GamePlayer implements IGamePlayer { private $name = ""; // 我的代理 IGamePlayer类型 private $proxy = null; public function __construct(String $_name) { $this->name = $_name; } /** * @Override */ public function login(String $user, String $password) { print_r("登录名为【" . $user . "】的用户【" . $this->name . "】登录成功!<br>"); } /** * @Override */ public function killBoss() { print_r($this->name . "在打怪!<br>"); } /** * @Override */ public function upgrade() { print_r($this->name . " 已升了一级!<br>"); } } //动态代理接口 Invocationhandler interface InvocationHandler{ public function invoke($proxy, $function_name, $param); } //动态代理类:DynamicProxy class GamePlayIH implements InvocationHandler { // 被代理实例Object private $obj = null; // 我要代理谁 public function __construct($_obj) { $this->obj = $_obj; } /** * @Override */ public function invoke($proxy, $function_name,$param){ //检查方法是否已定义 if ($proxy->hasMethod($function_name)){ //获取一个类方法的 ReflectionMethod对象。相当于new ReflectionMethod('class', 'myMethod') $method = $proxy->getMethod($function_name); if ($method->isPublic()&&!$method->isAbstract()&&count($param)<1){ if ($method->isStatic()){ //如果是静态方法,不需要传入调用的对象 $method->invoke(null); }else{ $method->invoke($this->obj); } }else{ if ($method->isStatic()){ //如果是静态方法,不需要传入调用的对象 $method->invokeArgs(null,$param); }else{ $method->invokeArgs($this->obj,$param); } } } } public function __call($function_name,$param){ $ref = new ReflectionClass($this->obj); $this->invoke($ref,$function_name,$param); } } class Client { public static function main() { // 定义一个痴迷的玩家 IGamePlayer $player = new GamePlayer("猿哥"); //InvocationHandler $proxy = new GamePlayIH($player); // 登录 $proxy->login("oneape15", "pwd123"); //开始杀怪 $proxy->killBoss(); // 升级 $proxy->upgrade(); } } Client::main(); 结果: 登录名为【oneape15】的用户【猿哥】登录成功! 猿哥在打怪! 猿哥 已升了一级! ```