HeadFir st 设计模式学习笔记12——代理模式
1.这⼀节的任务是我们需要完成对上⼀节的糖果机产生⼀个机器状况和余量的报告,若这个报告
在本地(不是通过I n tern et)生成的话,那么我们的设计就很简单了,在糖果机中加入Locat i on 的
信息,并且创建⼀个类Gu mbal l Mon i tor 完成报告的生成:
pu bl i c cl ass Gu mbal l Mon i tor {
Gu mbal l Mach i n e mach i n e;
pu bl i c Gu mbal l Mon i tor(Gu mbal l Mach i n e mach i n e) {
th i s.mach i n e = mach i n e;
}
pu bl i c v oi d report () {
Sy stem.ou t .pri n t l n ("Gu mbal l Mach i n e: " + mach i n e.getLocat i on ());
Sy stem.ou t .pri n t l n ("Cu rren t i n v en tory : " + mach i n e.getCou n t () + " gu mbal l s");
Sy stem.ou t .pri n t l n ("Cu rren t state: " + mach i n e.getState());
}
}
2.但是,我们的需求进⼀步加强——要求有⼀个远端可以获得这个报告,这有点RMI 的意思(具
体什么叫做RMI ,有什么特点请看这篇文字)那么我们就需要⼀个叫做远程代理的东西了。所谓
代理(prox y ),就是代表⼀个真实的对象,在这个例子中,代理就像是糖果机⼀样,但其实幕
后是它利用网络和⼀个远程的真正糖果机进行沟通。基本的想法如下图:
在这个场景中,客户对象所做的就像是直接操作远端⼀样,但是实际上它只是操作了本地堆上
的代理对象的方法,而代理则帮忙处理了所有网络的通信。
1) 制作远程接口:
pu bl i c i n terface Gu mbal l Mach i n eRemote extends Remote {/ /这个是对RMI有规定意义的,请遵
循。
pu bl i c i n t getCou n t () throws RemoteException; / /声明的所有方法都要抛出RemoteExceptio
n
pu bl i c String getLocat i on () th rows RemoteEx cept i on ; / /并且要确定返回或者传入的变量都是基
本类型或者可序列化的类型。
pu bl i c State getState() th rows RemoteEx cept i on ;
}
这里我们原来的State就无法序列化,那么我们对它做⼀些修改:
pu bl i c i n terface State extends Serial i z able {
pu bl i c v oi d i n sertQu arter();
pu bl i c v oi d ej ectQu arter();
pu bl i c v oi d tu rn Cran k();
pu bl i c v oi d di spen se();
}
在上⼀节中我们实现的各个状态内部都有⼀个糖果机的引用,而使用这个引用可以直接改变糖
果机所在的状态,我们不希望这样的能力通过序列化传到对端,也就是说,类中的部分内容我
们希望在序列化的时候忽略掉,那么我们可以使用t ran si en t关键字告诉JVM不要序列化这个字段
。
2)制作远程的实现:
这是在远端实际干活的家伙,它首先要继承Un i castRemoteObj ect类,以成为远程服务,其次它
还要实现Gu mbal l Mach i n eRemote 这个远程接口的实际功能。
pu bl i c cl ass Gu mbal l Mach i n e
ex ten ds Un i castRemoteObj ect i mpl emen t s Gu mbal l Mach i n eRemote
{
State sol dOu tState;
State n oQu arterState;
State h asQu arterState;
State sol dState;
State wi n n erState;
State state = sol dOu tState;
i n t cou n t = 0;
St ri n g l ocat i on ;
pu bl i c Gu mbal l Mach i n e(St ri n g l ocat i on , i n t n u mberGu mbal l s) throws RemoteException {/ /设
计⼀个构造方法来抛出异常,这是因为Un i castRemoteObj ect 就抛出异常,超类抛出异常时,子
类也只得抛出同样的异常。
sol dOu tState = n ew Sol dOu tState(th i s);
n oQu arterState = n ew NoQu arterState(th i s);
h asQu arterState = n ew HasQu arterState(th i s);
sol dState = n ew Sol dState(th i s);
wi n n erState = n ew Wi n n erState(th i s);
th i s. cou n t = n u mberGu mbal l s;
i f (n u mberGu mbal l s > 0) {
state = n oQu arterState;
}
th i s. l ocat i on = l ocat i on ;
}
pu bl i c v oi d i n sertQu arter() {
state. i n sertQu arter();
}
pu bl i c v oi d ej ectQu arter() {
state.ej ectQu arter();
}
pu bl i c v oi d tu rn Cran k() {
state. tu rn Cran k();
state.di spen se();
}
v oi d setState(State state) {
th i s. state = state;
}
v oi d rel easeBal l () {
Sy stem.ou t .pri n t l n ("A gu mbal l comes rol l i n g ou t th e sl ot . . . ");
i f (cou n t ! = 0) {
cou n t = cou n t - 1;
}
}
pu bl i c v oi d ref i l l (i n t cou n t ) {
th i s. cou n t = cou n t ;
state = n oQu arterState;
}
pu bl i c i n t getCou n t () {
retu rn cou n t ;
}
pu bl i c State getState() {
retu rn state;
}
pu bl i c St ri n g getLocat i on () {
retu rn l ocat i on ;
}
pu bl i c State getSol dOu tState() {
retu rn sol dOu tState;
}
pu bl i c State getNoQu arterState() {
retu rn n oQu arterState;
}
pu bl i c State getHasQu arterState() {
retu rn h asQu arterState;
}
pu bl i c State getSol dState() {
retu rn sol dState;
}
pu bl i c State getWi n n erState() {
retu rn wi n n erState;
}
pu bl i c St ri n g toSt ri n g() {
St ri n gBu f fer resu l t = n ew St ri n gBu f fer();
resu l t .appen d("/n Mi gh t y Gu mbal l , I n c. ");
resu l t .appen d("/n Jav a-en abl ed Stan di n g Gu mbal l Model #2004");
resu l t .appen d("/n I n v en tory : " + cou n t + " gu mbal l ");
i f (cou n t ! = 1) {
resu l t .appen d("s");
}
resu l t .appen d("/n ");
resu l t .appen d("Mach i n e i s " + state + "/n ");
retu rn resu l t . toSt ri n g();
}
}
3)我们可以把Serv er端的服务启动起来了,通过⼀个程序绑定⼀下:
pu bl i c cl ass Gu mbal l Mach i n eT estDri v e {
pu bl i c stat i c v oi d mai n (St ri n g[ ] args) {
Gu mbal l Mach i n eRemote gu mbal l Mach i n e = n u l l ;
i n t cou n t ;
i f (args. l en gth < 2) {
Sy stem.ou t .pri n t l n ("Gu mbal l Mach i n e ");
Sy stem.ex i t (1);
}
t ry {
cou n t = I n teger .parseI n t (args[1] );
gu mbal l Mach i n e =
n ew Gu mbal l Mach i n e(args[0] , cou n t );
Naming. rebind("/ / " + args[0] + "/gumbal lmachine", gumbal lMachine); / /这句话是关键
} cat ch (Ex cept i on e) {
e.pri n tStackT race();
}
}
}
首先我们在命令行中启动rmi regi st ry ,然后运行这个类,Serv er端就开始工作了。
4)我们开始编写客户端
我们对原有的客户端不需要太大的改动,
pu bl i c cl ass Gu mbal l Mon i tor {
Gumbal lMachineRemote mach i n e;
pu bl i c Gu mbal l Mon i tor(Gumbal lMachineRemote mach i n e) {/ /我们依赖于远程接口
th i s.mach i n e = mach i n e;
}
pu bl i c v oi d report () {
t ry {
Sy stem.ou t .pri n t l n ("Gu mbal l Mach i n e: " + mach i n e.getLocat i on ());
Sy stem.ou t .pri n t l n ("Cu rren t i n v en tory : " + mach i n e.getCou n t () + " gu mbal l s");
Sy stem.ou t .pri n t l n ("Cu rren t state: " + mach i n e.getState());
} cat ch (RemoteException e) {/ /对可能产生的网络异常进行捕获和处理
e.pri n tStackT race();
}
}
}
5)我们写⼀个监控系统,监控多个糖果机的状态,这时我们就彻底完成了该系统的设计和实现:
pu bl i c cl ass Gu mbal l Mon i torT estDri v e {
pu bl i c stat i c v oi d mai n (St ri n g[ ] args) {
St ri n g[ ] l ocat i on = {"rmi : / / san tafe.mi gh t y gu mbal l . com/gu mbal l mach i n e",
"rmi : / /bou l der .mi gh t y gu mbal l . com/gu mbal l mach i n e",
"rmi : / / seat t l e.mi gh t y gu mbal l . com/gu mbal l mach i n e"};
Gu mbal l Mon i tor[ ] mon i tor = n ew Gu mbal l Mon i tor[ l ocat i on . l en gth ] ;
for (i n t i =0; i < l ocat i on . l en gth ; i ++) {
t ry {
Gu mbal l Mach i n eRemote mach i n e =
(Gu mbal l Mach i n eRemote) Naming. lookup(l ocat i on [ i ] );
mon i tor[ i ] = n ew Gu mbal l Mon i tor(mach i n e);
Sy stem.ou t .pri n t l n (mon i tor[ i ] );
} cat ch (Ex cept i on e) {
e.pri n tStackT race();
}
}
for(i n t i =0; i < mon i tor . l en gth ; i ++) {
mon i tor[ i ] . report ();
}
}
}
6)举这个例子的目的其实是想阐明代理模式(远程代理模式只是⼀个特例),这个模式为另⼀
个对象提供⼀个替身或者占位符以控制对这个对象的访问。主要的代理模式有三种,我们会在
下边的部分介绍另两个:
远程代理控制访问远程对象
虚拟代理控制访问创建开销大的资源
保护代理基于权限控制对资源的访问
为了让程序真正使用代理而不是直接使用相关的类,⼀个常用的技巧是提供⼀个工厂。适配器
模式和代理模式都是挡在其他对象前边的方式,但是前者会改变对象适配的接口,而代理模式
则实现相同的接口。
7)我们下边介绍⼀下虚拟代理:
它作为创建开销大的对象的代表,当我们真正需要创建⼀个对象时才创建它,当对象在创建前
和创建中时,虚拟代理来扮演对象的替身,对象创建后,代理会将请求直接委托给对象。
我们设定这样⼀个例子,⼀个播放软件要从线上得到唱片的封套,这个动作是很耗时的,那么
我们可以设置⼀个Prox y ,当图片没有加载的时候就在界面上显示“正在加载”,而当图片加载完
毕以后(在这个时间点,真正绘图的类被实例化),Prox y 把所有关于图案显示的方法都委托给
真正的显示控制类。在以后这个真正绘图的类实例创建以后,下次再调用绘图代理就直接委托
它来完成了。下边我们举⼀个例子来说明(非原书例子),我们假设⼀个程序要使用Emai l 服务
,但是并不是什么时候都要去接入Emai l 的,而Emai l 的接入是非常耗时耗资源的⼀件事情,那
么这个场景就很合适于这个模式。
首先,我们要先设定⼀个公共接口,这个接口为代理和实际的实现提供了相同的方法定义:
pu bl i c i n terface EMai l Serv i ce {
pu bl i c v oi d sen dMai l (St ri n g recei v er , St ri n g su bj ect , St ri n g tex t );
pu bl i c v oi d recei v eMai l (St ri n g recei v er);
}
接着,我们实际来实现⼀个类,这个类实现了上边的接口,这个类需要的就是适时的载入,只
要当需要的时候再实例化这个类。当然在这里我们只是做示意性质的实现:
pu bl i c cl ass Real EMai l Serv i ce i mpl emen t s EMai l Serv i ce{
@Ov erri de
pu bl i c v oi d sen dMai l (St ri n g recei v er , St ri n g su bj ect , St ri n g tex t ) {
Sy stem.ou t .pri n t l n ("Sen di n g mai l to '" + recei v er + "'" + " wi th su bj ect '" +
su bj ect + "'" + " an d message '" + tex t + "'");
}
@Ov erri de
pu bl i c v oi d recei v eMai l (St ri n g recei v er) {
Sy stem.ou t .pri n t l n ("Recei v i n g mai l f rom '" + recei v er + "'");
}
}
再接着,我们为上边的这个类创建⼀个代理类,这个代理类必须遵循接口的方法定义。在许多
情况下,Cl i en t甚至不知道他们是在Prox y 上进行方法的调用的。在代理类中,要持有⼀个真实
实现的类的引用,以便在合适的时候将事情移交给实际的类实现。
public class ProxyEMailService implements EMailService{
private RealEMailService emailService;
@Override
public void receiveMail(String receiver) {
if (emailService == null){
emailService = new RealEMailService();
}
emailService.receiveMail(receiver);
}
@Override
public void sendMail(String receiver, String subject, String text) {
if (emailService == null){
emailService = new RealEMailService();
}
emailService.sendMail(receiver, subject, text);
}
}
我们就利用上述两个类完成⼀个程序的构建,在程序中,我们创建⼀个指向Prox y 的引用,虽然
它被声明为共同的接口:
public class Application {
public EMailService locateEMailService(){
EMailService emailService = new ProxyEMailService();//时时刻刻都透着面向接口编程的原则
return emailService;
}
}
这样,我们就完成了这个程序的设计,下边我们来测试⼀下:
public class ApplicationClient {
public static void main(String[] args) {
Application application = new Application();
EMailService emailService = application.locateEMailService();//注意,此时我们并没有真正创
emailService.sendMail("
[email protected]", "Hello", "A test mail");//当我们实际使用时我们才创
emailService.receiveMail("
[email protected]");
}
}
8)现在介绍保护代理Protect i on Prox y
Jav a中有⼀个叫做动态代理的技术,在j av a. l an g. ref l ect包中,这个机制和我们上边的有⼀点点差
别:
此时Jav a为你创建了Prox y 类,但是你没有想以前那样将控制代码放入Prox y 中,此时我们需要
将这些代码放到⼀个实现了I n v ocat i on Han dl er接口的I n v ocat i on Han dl er类中去。在介绍保护代理
时,我们就打算用这个技术来做具体的实现。
在具体介绍保护代理前,我们现在假设⼀个场景:
有⼀个系统,我们需要对每⼀个用户维护⼀套操作(比如setName、setGen der等等,需要注意
的是这个例子中的setHotOrNotRat i n g则正好相反,这是别人对你的评分,你自己无权评分),
每⼀个人都能修改自己的属性,而不能修改对方的属性,那么这样的接入保护是如何做到的呢
?
首先,我们先定义修改的相关动作作为共同的方法放入⼀个接口之中:
pu bl i c i n terface Person Bean {
St ri n g getName();
St ri n g getGen der();
St ri n g get I n terest s();
i n t getHotOrNotRat i n g();
v oi d setName(St ri n g n ame);
v oi d setGen der(St ri n g gen der);
v oi d set I n terest s(St ri n g i n terest s);
v oi d setHotOrNotRat i n g(i n t rat i n g);
}
接着,就像上边的实现步骤⼀样,我们实现这些方法的真正实现:
pu bl i c cl ass Person Bean I mpl i mpl emen t s Person Bean {
St ri n g n ame;
St ri n g gen der;
St ri n g i n terest s;
i n t rat i n g;
i n t rat i n gCou n t = 0;
pu bl i c St ri n g getName() {
retu rn n ame;
}
pu bl i c St ri n g getGen der() {
retu rn gen der;
}
pu bl i c St ri n g get I n terest s() {
retu rn i n terest s;
}
pu bl i c i n t getHotOrNotRat i n g() {
i f (rat i n gCou n t == 0) retu rn 0;
retu rn (rat i n g/ rat i n gCou n t );
}
pu bl i c v oi d setName(St ri n g n ame) {
th i s.n ame = n ame;
}
pu bl i c v oi d setGen der(St ri n g gen der) {
th i s.gen der = gen der;
}
pu bl i c v oi d set I n terest s(St ri n g i n terest s) {
th i s. i n terest s = i n terest s;
}
pu bl i c v oi d setHotOrNotRat i n g(i n t rat i n g) {
th i s. rat i n g += rat i n g;
rat i n gCou n t++;
}
}
下⼀步请注意,这里就有所区别了。再次提醒,现在我们是在使用Jav a内置的Prox y 代理模式—
—我们要创建两个I n v ocat i on Han dl er,I n v ocat i on Han dl er完成了Prox y 的行为,而具体创建实际的
Prox y 对象则是Jav a 自己替我们去做的事情了,我们仅仅需要提供⼀个Han dl er以便在⼀个方法
被调用时该做些什么。你可以把I n v ocat i on Han dl er想象为:当代理的方法被调用时,代理就会把
调用发给I n v ocat i on Han dl er,需要注意的是,此时代理不管调用什么方法都并不是直接调用I n v oc
at i on Han dl er的方法,而是调用了它自身的i n v oke方法来处理。
我们首先设计拥有者:
pu bl i c cl ass Own erI n v ocat i on Han dl er i mpl emen t s I n v ocat i on Han dl er {
PersonBean person; / /我们保持⼀个引用在每⼀个InvocationHandler中
pu bl i c Own erI n v ocat i on Han dl er(Person Bean person ) {
th i s.person = person ;
}
pu bl i c Obj ect i n v oke(Obj ect prox y , Meth od meth od, Obj ect [ ] args) / /每⼀次Prox y 的方法被调用
就会导致Prox y 调用这个方法
th rows I l l egal AccessEx cept i on {
t ry {
i f (meth od.getName(). startsWi th("get ")) {/ /若⼀个方法是⼀个get ter,那么不需要对其进行
访问控制,所以就直接调用
retu rn meth od. i n v oke(person , args);
} el se i f (meth od.getName().equ al s(" setHotOrNotRating")) {/ /这个为什么请看上边的场
景说明
th row n ew I l l egal AccessEx cept i on ();
} el se i f (meth od.getName(). start sWi th ("set ")) {/ /若是set ter,因为是own er,就直接调用
retu rn meth od. i n v oke(person , args);
}
} cat ch (I n v ocat i on T argetEx cept i on e) {
e.pri n tStackT race();
}
retu rn n u l l ;
}
}
接着,我们设计非拥有者:
pu bl i c cl ass Non Own erI n v ocat i on Han dl er i mpl emen t s I n v ocat i on Han dl er {
Person Bean person ;
pu bl i c Non Own erI n v ocat i on Han dl er(Person Bean person ) {
th i s.person = person ;
}
pu bl i c Obj ect i n v oke(Obj ect prox y , Meth od meth od, Obj ect [ ] args)
th rows I l l egal AccessEx cept i on {
t ry {
i f (meth od.getName(). start sWi th ("get ")) {
retu rn meth od. i n v oke(person , args);
} el se i f (meth od.getName().equ al s("setHotOrNotRat i n g")) {
return method. invoke(person, args); / /与Owner相比,此处的处理与下边的setter换
⼀下
} el se i f (meth od.getName(). start sWi th ("set ")) {
throw new I l legalAccessException();
}
} cat ch (I n v ocat i on T argetEx cept i on e) {
e.pri n tStackT race();
}
retu rn n u l l ;
}
}
好了,我们现在可以真正创建⼀个Prox y 并且测试⼀下我们的程序,使用Jav a自带的Prox y 类中
的 newProxyInstance静态方法创建代理,这个方法如下:
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHa
ndler h)
第⼀个参数是传入类的加载器,第二个参数是传入类的接口,第三个是告诉它该使用什么样的InvocationHandler
进行处理。
pu bl i c cl ass Mat ch Maki n gT estDri v e {
Hash tabl e dat i n gDB = n ew Hash tabl e();
pu bl i c stat i c v oi d mai n (St ri n g[ ] args) {
Mat ch Maki n gT estDri v e test = n ew Mat ch Maki n gT estDri v e();
test .dri v e();
}
pu bl i c Mat ch Maki n gT estDri v e() {
ini tial i z eDatabase(); / /初始化⼀些数据
}
pu bl i c v oi d dri v e() {/ /开始运行
Person Bean j oe = getPerson FromDatabase("Joe Jav abean ");
Person Bean own erProx y = getOwn erProx y (j oe);
Sy stem.ou t .pri n t l n ("Name i s " + own erProx y .getName());
own erProx y . set I n terest s("bowl i n g, Go");
Sy stem.ou t .pri n t l n ("I n terest s set f rom own er prox y ");
t ry {
own erProx y . setHotOrNotRat i n g(10);
} cat ch (Ex cept i on e) {
Sy stem.ou t .pri n t l n ("Can 't set rat i n g f rom own er prox y ");
}
Sy stem.ou t .pri n t l n ("Rat i n g i s " + own erProx y .getHotOrNotRat i n g());
Person Bean n on Own erProx y = getNon Own erProx y (j oe);
Sy stem.ou t .pri n t l n ("Name i s " + n on Own erProx y .getName());
t ry {
n on Own erProx y . set I n terest s("bowl i n g, Go");
} cat ch (Ex cept i on e) {
Sy stem.ou t .pri n t l n ("Can 't set i n terest s f rom n on own er prox y ");
}
n on Own erProx y . setHotOrNotRat i n g(3);
Sy stem.ou t .pri n t l n ("Rat i n g set f rom n on own er prox y ");
Sy stem.ou t .pri n t l n ("Rat i n g i s " + n on Own erProx y .getHotOrNotRat i n g());
}
PersonBean getOwnerProxy(PersonBean person) {/ /设置Owner代理
retu rn (PersonBean) Proxy .newProxyInstance(
person .getCl ass().getCl assLoader(),
person .getCl ass().get I n terfaces(),
n ew Own erI n v ocat i on Han dl er(person ));
}
PersonBean getNonOwnerProxy(PersonBean person) {/ /设置非Owner代理
retu rn (Person Bean ) Prox y .n ewProx y I n stan ce(
person .getCl ass().getCl assLoader(),
person .getCl ass().get I n terfaces(), / /
n ew Non Own erI n v ocat i on Han dl er(person )); / /设置处理的方式
}
Person Bean getPerson FromDatabase(St ri n g n ame) {
retu rn (Person Bean )dat i n gDB.get (n ame);
}
v oi d i n i t i al i z eDatabase() {/ /首先我们要有⼀些数据进行操作
Person Bean j oe = n ew Person Bean I mpl ();
j oe. setName("Joe Jav abean ");
j oe. set I n terest s("cars, compu ters, mu si c");
j oe. setHotOrNotRat i n g(7);
dat i n gDB.pu t (j oe.getName(), j oe);
Person Bean kel l y = n ew Person Bean I mpl ();
kel l y . setName("Kel l y Kl osu re");
kel l y . set I n terest s("ebay , mov i es, mu si c");
kel l y . setHotOrNotRat i n g(6);
dat i n gDB.pu t (kel l y .getName(), kel l y );
}
}
这样我们就完成了保护代理的任务,代码在执行时还没有Prox y 类,它是根据需要从传入的接口
中创建的。