设计模式(二)代理模式
代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义如下:
provide a surrogate or placeholder for another object to control access to it.(为其它对象提供一种代理以控制对这个对象的访问。)
代理模式的UML图如图所示。
代理模式也叫做委托模式,它是一项基本设计技巧,许多其它的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。先看一下UML图中四个角色的定义:
- Subject 抽象主题角色
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。 - RealSubject 真实主题角色
也叫做被委托角色、被代理角色,它才是冤大头,是业务逻辑的具体执行者。 - Proxy 代理主题角色
也叫做委托类、代理类,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题处理完毕前后做预处理和善后处理工作。 - Client 客户端
使用代理类和主题接口完成一些工作。
我们首先来看Subject抽象主题类的通用源码,如代码清单1.2-1所示。
代码清单1.2-1抽象主题类
1 class CSubject{ 2 public: 3 CSubject(void){}; 4 ~CSubject(void){}; 5 6 //定义一个方法 7 void request(); 8 9 };
在抽象主题类中定义了一个方法request来作为方法的代表,RealSubject对它进行实现,如代码清单1.2-2所示。
代码清单1.2-2 真实主题类
1 class CRealSubject :public CSubject{ 2 public: 3 CRealSubject(void){}; 4 ~CRealSubject(void){}; 5 6 //实现方法 7 void request(){ 8 //业务逻辑处理... 9 } 10 };
RealSubject是一个正常的业务实现类,代理模式的核心就在代理类上,如代码清单1.2-3所示。
1 class CProxy :public CSubject{ 2 //要代理哪个主题类 3 private: 4 CSubject *m_subject; 5 public: 6 //通过构造函数传递给代理类,要代理谁 7 CProxy(CSubject *pSubject){ 8 this->m_subject = pSubject; 9 }; 10 11 //实现抽象主题类定义的方法 12 void request(){ 13 this->m_subject->request(); 14 }; 15 };
一个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代理哪个真实主题角色,是由场景类决定的,当然,最简单的情况就是一个主题类一个代理类。在通常情况下, 一个接口只需要一个代理类就可以了,具体代理哪个哪个实现类由高层模块来决定,也就是在代理类的构造函数中传递被代理者,例如我们可以在代理类Proxy中的构造函数中产生代理的实例,你要代理谁,产生该代理的实例,然后把被代理者传递进来,该模式在实际的项目应用中比较广泛。
代理模式的优点
- 职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其它非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。 - 高扩展性
具体主题角色是随时都会发生变化的,但只要它实现了接口,不管如何变化,代理类都可以在不做任何修改的情况下使用。 - 智能化
主要体现在动态代理中。
代理模式的应用场合
1、远程代理,也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。比如说 WebService,当我们在应用程序的项目中加入一个 Web 引用,引用一个 WebService,此时会在项目中声称一个 WebReference 的文件夹和一些文件,这个就是起代理作用的,这样可以让那个客户端程序调用代理解决远程访问的问题;
2、虚拟代理,是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化,比如打开一个网页,这个网页里面包含了大量的文字和图片,但我们可以很快看到文字,但是图片却是一张一张地下载后才能看到,那些未打开的图片框,就是通过虚拟代里来替换了真实的图片,此时代理存储了真实图片的路径和尺寸;
3、安全代理,用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候;
4、指针引用,是指当调用真实的对象时,代理处理另外一些事。比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它,或当第一次引用一个持久对象时,将它装入内存,或是在访问一个实际对象前,检查是否已经释放它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些内务处理;
5、延迟加载,用代理模式实现延迟加载的一个经典应用就在 Hibernate 框架里面。当 Hibernate 加载实体 bean 时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采取延迟加载的机制,以提高系统的性能。Hibernate 中的延迟加载主要分为属性的延迟加载和关联表的延时加载两类。实现原理是使用代理拦截原有的 getter 方法,在真正使用对象数据时才去数据库或者其他第三方组件加载实际的数据,从而提升系统性能。
举个例子
比如西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理。(这个有意思的例子出自一本电子书《24种设计模式介绍与6大设计原则.pdf》)
涉及到的对象如下:
main(),西门庆
CSubject,接口
CWangPo,代理王婆
CPanJinLian,实际执行者之一,潘金莲
CJiaShi,实际执行者之二,贾氏(书作者的脑洞略大...)
说明:代理和实际执行者派生于共同的接口,代理拥有实际执行者的实例。代理的每一个函数(接口的实现函数),直接调用实际执行者的对应接口函数。
注意:代理只是简单的装载,然后调用实际执行者的函数。
代码如下:
先定义一种类型的女人:
subject.h 抽象主题类CSubject:
1 #ifndef _SUBJECT_H_ 2 #define _SUBJECT_H_ 3 class CSubject{ 4 public: 5 CSubject(void){}; 6 ~CSubject(void){}; 7 //这种类型的女人能做什么事情呢? 8 //定义为纯虚函数,在抽象类中不实现,在子类中实现 9 virtual void makeEyesWithMan()=0;//抛媚眼 10 virtual void happyWithMan()=0;//这个作者真的脏 11 }; 12 #endif
然后定义潘氏和贾氏:
realsubject.h 真实主题类CPanJianLian,CJiaShi:
1 #ifndef _REALSUBJECT_H_ 2 #define _REALSUBJECT_H_ 3 #include "subject.h" 4 5 6 class CPanJianLian :public CSubject{ 7 public: 8 CPanJianLian(void){}; 9 ~CPanJianLian(void){}; 10 void makeEyesWithMan(void); 11 void happyWithMan(void); 12 }; 13 14 class CJiaShi :public CSubject{ 15 public: 16 CJiaShi(void){}; 17 ~CJiaShi(void){}; 18 void makeEyesWithMan(void); 19 void happyWithMan(void); 20 }; 21 #endif
真实主题类具体做的事情:
realsubject.cpp
1 #include "realsubject.h" 2 #include <iostream> 3 using namespace std; 4 5 void CPanJianLian::makeEyesWithMan() 6 { 7 cout << "PanJianLian leer at ogle to man." << endl; 8 } 9 10 void CPanJianLian::happyWithMan() 11 { 12 cout << "PanJianLian is making love with someone." << endl; 13 } 14 15 void CJiaShi::makeEyesWithMan() 16 { 17 cout << "JiaShi leer at ogle to man." << endl; 18 } 19 20 void CJiaShi::happyWithMan() 21 { 22 cout << "JiaShi is making love with someone." << endl; 23 }
再定一个代理(王婆):
proxy.h 代理类 CWangPo:
1 #ifndef _PROXY_H_ 2 #define _PROXY_H_ 3 #include "subject.h" 4 #include "realsubject.h" 5 6 class CWangPo :public CSubject{ 7 public: 8 //默认代理PanJianLian 9 CWangPo(void){ 10 this->m_pSubject = new CPanJianLian(); 11 } 12 //可以代理任何这种类型的女子 13 CWangPo(CSubject* pSubject); 14 ~CWangPo(void); 15 void makeEyesWithMan(void); 16 void happyWithMan(void); 17 private: 18 CSubject* m_pSubject; 19 }; 20 #endif
如何代理?可以重写makeEyesWithMan()和happyWithMan()函数,在函数中指定真实主题类去干活:
proxy.cpp
1 #include "proxy.h" 2 3 CWangPo::CWangPo(CSubject* pSubject) 4 { 5 this->m_pSubject = pSubject; 6 } 7 8 CWangPo::~CWangPo(void) 9 { 10 delete this->m_pSubject; 11 } 12 void CWangPo::makeEyesWithMan() 13 { 14 this->m_pSubject->makeEyesWithMan(); 15 } 16 17 void CWangPo::happyWithMan() 18 { 19 this->m_pSubject->happyWithMan(); 20 }
两个女主角都上场了,男主角也该出现了:
main.cpp
1 #include <iostream> 2 #include <tchar.h> 3 #include "subject.h" 4 #include "proxy.h" 5 #include "realsubject.h" 6 using namespace std; 7 8 int _tmain(int argc, _TCHAR* argv[]) 9 { 10 cout << "\n\n\n\n" << endl; 11 //call WangPo 12 CWangPo* pWangPo; 13 //Ximen wanted to do something with PanJianLian,Wangpo arranged. 14 pWangPo = new CWangPo(new CPanJianLian); 15 // pWangPo = new CWangPo(); 16 pWangPo->makeEyesWithMan(); 17 pWangPo->happyWithMan(); 18 delete pWangPo; 19 cout << "\n\n\n\n" << endl; 20 //Ximen wanted to do someting with JiaShi,WangPo arranged. 21 pWangPo = new CWangPo(new CJiaShi); 22 pWangPo->makeEyesWithMan(); 23 pWangPo->happyWithMan(); 24 delete pWangPo; 25 26 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); 27 _CrtDumpMemoryLeaks(); 28 29 getchar(); 30 return 0; 31 }
运行结果:
代理模式主要使用了C++的多态,干活的是被代理类,代理类主要是接活。
看来代理模式的结构和策略模式类似,都是由一个类来装载接口的实例,策略模式是CContext来装载,代理模式是CWangPo来装载。CContext不是从CStrategy派生的,所以不需要实现CStrategy接口函数,而CWangPo是从CSubject派生的,所以CWangPo很清楚CPanJianLian和CJiaShi的接口函数。这就是代理,代理人知道被代理人能干的事情,即函数,所以代理人可以称为中介。
代理模式可以很好地将前后端分开,实现了松散耦合。代理模式属于结构型模式。