代理模式(Proxy)
<意图>
【GOF】为其他对象提供一种代理对象以控制对这个对象的访问。
代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象(被代理对象)的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。 (客户端引用的是代理对象)某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题 对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系 统的其他角色代为创建并传入。
<动机>
因为某个对象消耗太多资源,而且你的代码并不是每个逻辑路径都需要此对象, 你曾有过延迟创建对象的想法吗 ( if和else就是不同的两条逻辑路径) ? 你有想过限制访问某个对象,也就是说,提供一组方法给普通用户,特别方法给管理员用户?以上两种需求都非常类似,并且都需要解决一个更大的问题:你如何提供一致的接口给某个对象让它可以改变其内部功能,或者是从来不存在的功能?
你怎样才能在不直接操作对象的情况下,对此对象进行访问?
<UML>
<总结>
个人理解实际应用中就是相当于公有的放到一个类中,客户访问不到,但是适当的时候需要调用,特殊的私有的放在各自类中用到时调用。特殊的类就是代理类需要有公有类的引用。只需要在用到的时候才取, client只需要引用代理对象。
两种情况:
一、引用不到寻求代理。
二、聚合共用部分,用到才调用,与继承相比增加对象开销但调用快,代码可复用。
<代理模式与父类和接口的异同>
<相同点>
代理模式的作用和父类以及接口的作用类似,都是为了聚合共用部分,减少公共部分的代码。
<不同点>
相比起父类,他们的语境不同,父类要表达的含义是 is-a, 而代理要表达的含义更接近于接口, 是 has-a,而且使用代理的话应了一句话"少用继承,多用组合",要表达的意思其实也就是降低耦合度了。
相比起接口,他们实现的功能又不太一样,语境都是has-a,不过接口是has-a-function,而代理对象时是has-a-object,这个 object是has-a-function的object,此外,接口是为了说明这个类拥有什么功能,却没有具体实现,实现了多态,而代理对象不但拥有 这个功能,还拥有这个功能的具体实现。
<示例一>
class Subject { function someMethod() { echo "test"; //sleep(1); //do something } } class ProxySubject { private $subject; function ProxySubject() { $this->subject = new Subject(); } function someMethod() { $this->subject->someMethod(); } } $proxy = new ProxySubject(); $proxy->someMethod();
<结果>
Result:test
<示例二>
class Printer { //代理对象,一台打印机 public function printSth() { echo 'I can print <br>'; } // some more function below // ... } class TextShop { //这是一个文印处理店,只文印,卖纸,不照相 private $printer; public function __construct(Printer $printer) { $this->printer = $printer; } public function sellPaper() { //卖纸 echo 'give you some paper <br>'; } public function __call($method, $args) { //将代理对象有的功能交给代理对象处理,即文印工作 if(method_exists($this->printer, $method)) { $this->printer->$method($args); } } } class PhotoShop { //这是一个照相店,只文印,拍照,不卖纸 private $printer; public function __construct(Printer $printer) { $this->printer = $printer; } public function takePhotos() { //照相 echo 'take photos for you <br>'; } public function __call($method, $args) { //将代理对象有的功能交给代理对象处理,即文印工作 if(method_exists($this->printer, $method)) { $this->printer->$method($args); } } } $printer = new Printer(); $textShop = new TextShop($printer); $photoShop = new PhotoShop($printer); $textShop->printSth(); $textShop->sellPaper(); $photoShop->printSth(); $photoShop->takePhotos();
文印处理店和照相店都具有文印的功能,所以我们可以将文印的功能代理给一台打印机,这里打印机只有一个功能,假如打印机还有n个功能,我们使用__call()方法就能够省去很多重复的代码了假如是使用继承,这样语境上就不合理,一个店显然不应该继承一台打印机而使用接口,因为我们的功能实现都是一样,也没有必要去重新实现接口的功能,所以此处使用代理,或者我们说是组合,是最佳选择。
上例中调试存在一个问题,就是$args是一个数组,需要将数组遍历出来如下改动
class Printer { //代理对象,一台打印机 public function printSth($a) { echo $a.'<br> I can print <br>'; } } class TextShop { //这是一个文印处理店,只文印,卖纸,不照相 private $printer; public function __construct(Printer $printer) { $this->printer = $printer; } public function sellPaper() { //卖纸 echo 'give you some paper <br>'; } public function __call($method, $args) { //将代理对象有的功能交给代理对象处理,即文印工作 //return call_user_func_array(array($this->printer,$method),$args); if(method_exists($this->printer, $method)) { foreach($args as $arg){ $this->printer->$method($arg); } } } } class PhotoShop { //这是一个照相店,只文印,拍照,不卖纸 private $printer; public function __construct(Printer $printer) { $this->printer = $printer; } public function takePhotos() { //照相 echo 'take photos for you <br>'; } public function __call($method, $args) { //将代理对象有的功能交给代理对象处理,即文印工作 //return call_user_func_array(array($this->printer,$method),$args); if(method_exists($this->printer, $method)) { foreach($args as $arg){//添加遍历 $this->printer->$method($arg); } } } } $printer = new Printer(); $textShop = new TextShop($printer); $photoShop = new PhotoShop($printer); $textShop->printSth(1); $textShop->sellPaper(); $photoShop->printSth(2); $photoShop->takePhotos();
<结果>
1 I can print give you some paper 2 I can print take photos for you