「補(bǔ)課」進(jìn)行時(shí):設(shè)計(jì)模式(5)——從 LOL 中學(xué)習(xí)代理模式

1. 從 LOL 中學(xué)習(xí)代理模式
我是一個(gè)很喜歡玩游戲的人,雖然平時(shí)玩游戲的時(shí)間并不多,但我也是一個(gè)忠實(shí)的 LOL 的愛好者,就是段位有點(diǎn)慘不忍睹,常年倔強(qiáng)的黑鐵,今年 S10 的總決賽在上海舉行,這個(gè)事兒我從 S9 就開始期待,結(jié)果門票今年沒賣,直接是抽簽拼人品。
360w+ 人抽 3600+ 人,這個(gè)概率屬實(shí)有點(diǎn)低,只能找個(gè)地方和我的小伙伴一起看了。

打 LOL 最開心的事情莫過于拿到 PentaKill 和 victory ,把這件事情使用代碼表現(xiàn)出來,首先定義一個(gè)玩游戲的人的接口:
public?interface?ILOLPlayer?{
????//?登錄使用用戶名和密碼
????void?login(String?name,?String?password);
????//?拿到五殺
????void?pentaKill();
????//?游戲勝利
????void?victory();
}
第二步對(duì)上面的接口做一個(gè)實(shí)現(xiàn):
public?class?LOLPlayer?implements?ILOLPlayer?{
????private?String?name?=?"";
????public?LOLPlayer(String?name)?{
????????this.name?=?name;
????}
????@Override
????public?void?login(String?name,?String?password)?{
????????System.out.println("登錄游戲:name:"?+?name?+?",?password:"?+?password);
????}
????@Override
????public?void?pentaKill()?{
????????System.out.println(this.name?+?"?拿到五殺啦?。?!");
????}
????@Override
????public?void?victory()?{
????????System.out.println(this.name?+?"?游戲勝利啦!??!");
????}
}
最后我們寫一個(gè)最簡單的測試類:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????LOLPlayer?lolPlayer?=?new?LOLPlayer("geekdigging");
????????lolPlayer.login("geekdigging",?"password");
????????lolPlayer.pentaKill();
????????lolPlayer.victory();
????}
}
運(yùn)行結(jié)果:
登錄游戲:name:geekdigging, password:password
geekdigging 拿到五殺啦!??!
geekdigging 游戲勝利啦?。?!
在打游戲的過程中,大家都知道有一個(gè)類型叫做排位賽,排位賽能到多少段位,一個(gè)是看時(shí)間,一個(gè)是看天賦,基本上打到一定的段位就很難再往上走了,如果說這時(shí)候還想升段位,那就只能取找代練幫忙做代打了。
我們找一位代練幫我們繼續(xù)打游戲:
public?class?LOLPlayerProxy?implements?ILOLPlayer?{
????private?ILOLPlayer?ilolPlayer;
????public?LOLPlayerProxy(LOLPlayer?playerLayer)?{
????????this.ilolPlayer?=?playerLayer;
????}
????@Override
????public?void?login(String?name,?String?password)?{
????????this.ilolPlayer.login(name,?password);
????}
????@Override
????public?void?pentaKill()?{
????????this.ilolPlayer.pentaKill();
????}
????@Override
????public?void?victory()?{
????????this.ilolPlayer.victory();
????}
}
我們稍微修改一下測試類:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????LOLPlayer?lolPlayer?=?new?LOLPlayer("geekdigging");
????????LOLPlayerProxy?proxy?=?new?LOLPlayerProxy(lolPlayer);
????????proxy.login("geekdigging",?"password");
????????proxy.pentaKill();
????????proxy.victory();
????}
}
這個(gè)測試類里面,我們沒有自己打游戲,而是使用代練 proxy 來幫我們打游戲,最后的結(jié)果是:
登錄游戲:name:geekdigging, password:password
geekdigging 拿到五殺啦?。。?br>geekdigging 游戲勝利啦?。。?br>這就是代理模式,本來需要自己做事情,使用代理以后,就可以由代理幫我們做事情了。
2. 代理模式定義
代理模式(Proxy Pattern)是一個(gè)使用率非常高的模式,其定義如下:
Provide a surrogate or placeholder for another object to control access toit.(為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問。)

Subject: 抽象主題角色。 RealSubject: 具體主題角色。 Proxy: 代理主題角色。
通用示例代碼如下:
//?抽象主題類,定義一個(gè)方法
public?interface?Subject?{
????void?request();
}
//?具體主題類,在這里寫具體的處理邏輯
public?class?RealSubject?implements?Subject?{
????@Override
????public?void?request()?{
????????//?邏輯處理
????}
}
//?代理類
public?class?Proxy?implements?Subject?{
????private?Subject?subject;
????public?Proxy()?{
????????this.subject?=?new?Proxy();
????}
????public?Proxy(RealSubject?subject)?{
????????this.subject?=?subject;
????}
????@Override
????public?void?request()?{
????????this.before();
????????this.subject.request();
????????this.after();
????}
????private?void?before()?{
????????//?邏輯預(yù)處理
????}
????private?void?after()?{
????????//?邏輯善后處理
????}
}
在最后的這個(gè)代理類中,通過構(gòu)造函數(shù)來進(jìn)行代理角色的傳遞,同時(shí)還可以在具體的處理邏輯上構(gòu)造一個(gè)切面,定義預(yù)處理邏輯以及善后處理邏輯。
3. 代理模式的優(yōu)點(diǎn)
職責(zé)清晰:真實(shí)的角色是用來實(shí)現(xiàn)具體業(yè)務(wù)邏輯的,無需關(guān)心其他工作,可以后期通過代理的方式來完成其他的工作。 高擴(kuò)展性: 智能化:
4. 普通代理
首先說普通代理,它的要求就是客戶端只能訪問代理角色,而不能訪問真實(shí)角色,這是比較簡單的。
使用上面最開始的打 LOL 進(jìn)行改造,我自己作為一個(gè)游戲玩家,我肯定自己不練級(jí)了,也就是場景類不能再直接 new 一個(gè) LOLPlayer 對(duì)象了,它必須由 LOLPlayerProxy 來進(jìn)行模擬場景。
首先是對(duì) LOLPlayer 類進(jìn)行改造,把 LOLPlayer 這個(gè)類的構(gòu)造方法修改,使他不能直接 new 一個(gè)對(duì)象出來。
public?class?LOLPlayer?implements?ILOLPlayer?{
????private?String?name;
????public?LOLPlayer(ILOLPlayer?ilolPlayer,?String?name)?throws?Exception?{
????????if?(ilolPlayer?==?null)?{
????????????throw?new?Exception("不能創(chuàng)建真實(shí)的角色");
????????}?else?{
????????????this.name?=?name;
????????}
????}
????//?省略剩余的代碼
}
接下來是代理類:
public?class?LOLPlayerProxy?implements?ILOLPlayer?{
????private?ILOLPlayer?iloLPlayer;
????public?LOLPlayerProxy(String?name)?{
????????try?{
????????????iloLPlayer?=?new?LOLPlayer(this,?name);
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
????}
????//?省略剩余的代碼
}
代理類也是僅修改了構(gòu)造函數(shù),通過傳進(jìn)來的一個(gè)代理者的名稱,就能進(jìn)行代理,在這種改造下,系統(tǒng)更加簡潔了,調(diào)用者只知道代理存在就可以,不用知道代理了誰。
最后的測試類也需要進(jìn)行修改:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????ILOLPlayer?proxy?=?new?LOLPlayerProxy("geekdigging");
????????proxy.login("geekdigging",?"password");
????????proxy.pentaKill();
????????proxy.victory();
????}
}
在這個(gè)代理類上,我沒有再去 new 一個(gè) LOLPlayer 的對(duì)象,即可對(duì) LOLPlayer 進(jìn)行代理。
5. 強(qiáng)制代理
強(qiáng)制代理實(shí)際上一個(gè)普通代理模式的變種,普通代理是通過代理找到真實(shí)的角色,但是強(qiáng)制代理卻是要「強(qiáng)制」,必須通過真實(shí)角色查找到代理角色,否則將不能訪問。
首先是對(duì)接口類加一個(gè) getProxy() 方法,指定要訪問自己必須通過哪個(gè)代理。
public?interface?ILOLPlayer?{
????//?登錄使用用戶名和密碼
????void?login(String?name,?String?password);
????//?拿到五殺
????void?pentaKill();
????//?游戲勝利
????void?victory();
????//?獲取自己的代理類
????ILOLPlayer?getProxy();
}
然后再是對(duì)具體實(shí)現(xiàn)類的改造:
public?class?LOLPlayer?implements?ILOLPlayer?{
????private?String?name;
????private?ILOLPlayer?proxy;
????public?LOLPlayer(String?name)?{
????????this.name?=?name;
????}
????@Override
????public?void?login(String?name,?String?password)?{
????????if?(this.isProxy())?{
????????????System.out.println("登錄游戲:name:"?+?name?+?",?password:"?+?password);
????????}?else?{
????????????System.out.println("請(qǐng)使用指定的代理");
????????}
????}
????@Override
????public?void?pentaKill()?{
????????if?(this.isProxy())?{
????????????System.out.println(this.name?+?"?拿到五殺啦?。?!");
????????}?else?{
????????????System.out.println("請(qǐng)使用指定的代理");
????????}
????}
????@Override
????public?void?victory()?{
????????if?(this.isProxy())?{
????????????System.out.println(this.name?+?"?游戲勝利啦!??!");
????????}?else?{
????????????System.out.println("請(qǐng)使用指定的代理");
????????}
????}
????@Override
????public?ILOLPlayer?getProxy()?{
????????this.proxy?=?new?LOLPlayerProxy(this);
????????return?this.proxy;
????}
????private?boolean?isProxy()?{
????????if?(this.proxy?==?null)?{
????????????return?false;
????????}?else?{
????????????return?true;
????????}
????}
}
這里增加了一個(gè)私有方法,檢查是否是自己指定的代理,是指定的代理則允許訪問,否則不允許訪問。
接下來是強(qiáng)制代理類的改進(jìn):
public?class?LOLPlayerProxy?implements?ILOLPlayer?{
????private?ILOLPlayer?iloLPlayer;
????public?LOLPlayerProxy(ILOLPlayer?iloLPlayer)?{
????????this.iloLPlayer?=?iloLPlayer;
????}
????@Override
????public?void?login(String?name,?String?password)?{
????????this.iloLPlayer.login(name,?password);
????}
????@Override
????public?void?pentaKill()?{
????????this.iloLPlayer.pentaKill();
????}
????@Override
????public?void?victory()?{
????????this.iloLPlayer.victory();
????}
????@Override
????public?ILOLPlayer?getProxy()?{
????????return?this;
????}
}
最后一個(gè)是測試類:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????test1();
????????test2();
????????test3();
????}
????public?static?void?test1()?{
????????ILOLPlayer?iloLPlayer?=?new?LOLPlayer("geekdigging");
????????iloLPlayer.login("geekdigging",?"password");
????????iloLPlayer.pentaKill();
????????iloLPlayer.victory();
????}
????public?static?void?test2()?{
????????ILOLPlayer?iloLPlayer?=?new?LOLPlayer("geekdigging");
????????ILOLPlayer?proxy?=?new?LOLPlayerProxy(iloLPlayer);
????????proxy.login("geekdigging",?"password");
????????proxy.pentaKill();
????????proxy.victory();
????}
????public?static?void?test3()?{
????????ILOLPlayer?iloLPlayer?=?new?LOLPlayer("geekdigging");
????????ILOLPlayer?proxy?=?iloLPlayer.getProxy();
????????proxy.login("geekdigging",?"password");
????????proxy.pentaKill();
????????proxy.victory();
????}
}
這里我寫了三個(gè)測試方法,分別是 test1 、 test2 和 test3 ,執(zhí)行一下這個(gè)測試類,結(jié)果如下:
請(qǐng)使用指定的代理
請(qǐng)使用指定的代理
請(qǐng)使用指定的代理
請(qǐng)使用指定的代理
請(qǐng)使用指定的代理
請(qǐng)使用指定的代理
登錄游戲:name:geekdigging, password:password
geekdigging 拿到五殺啦?。?!
geekdigging 游戲勝利啦!?。?br>可以發(fā)現(xiàn),前兩個(gè)方法都沒有正常產(chǎn)生訪問, test1 是直接 new 了一個(gè)對(duì)象,無法成功訪問,而 test2 雖然是使用了代理,但是結(jié)果還是失敗了,因?yàn)樗付ǖ牟⒉皇钦鎸?shí)的對(duì)象,這個(gè)對(duì)象是我們自己手動(dòng) new 出來的,當(dāng)然不行,只有最后一個(gè) test3 是可以正常代理對(duì)象的。
強(qiáng)制代理的概念就是要從真實(shí)角色查找到代理角色,不允許直接訪問真實(shí)角色。高層模塊只要調(diào)用 getProxy 就可以訪問真實(shí)角色的所有方法,它根本就不需要產(chǎn)生一個(gè)代理出來,代理的管理已經(jīng)由真實(shí)角色自己完成。
6. 動(dòng)態(tài)代理
動(dòng)態(tài)代理是在實(shí)現(xiàn)階段不用關(guān)心代理誰,而在運(yùn)行階段才指定代理哪一個(gè)對(duì)象。相對(duì)來說,自己寫代理類的方式就是靜態(tài)代理。
實(shí)現(xiàn)動(dòng)態(tài)代理,主要有兩種方式,一種是通過 JDK 為我們提供的 InvocationHandler 接口,另一種是使用 cglib 。
把上面的案例接著改成動(dòng)態(tài)代理的方式:
增加一個(gè) LOLPlayIH 動(dòng)態(tài)代理類,來實(shí)現(xiàn) InvocationHandler 接口。
public?class?LOLPlayIH?implements?InvocationHandler?{
????Object?object;
????public?LOLPlayIH(Object?object)?{
????????this.object?=?object;
????}
????@Override
????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????Object?result?=?method.invoke(this.object,?args);
????????return?result;
????}
}
這里的 invoke 方法是接口 InvocationHandler 定義必須實(shí)現(xiàn)的,它完成對(duì)真實(shí)方法的調(diào)用。
接下來是測試類:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????ILOLPlayer?ilolPlayer?=?new?LOLPlayer("geekdigging");
????????InvocationHandler?handler?=?new?LOLPlayIH(ilolPlayer);
????????ClassLoader?loader?=?ilolPlayer.getClass().getClassLoader();
????????ILOLPlayer?proxy?=?(ILOLPlayer)?Proxy.newProxyInstance(loader,?new?Class[]?{ILOLPlayer.class},?handler);
????????proxy.login("geekdigging",?"password");
????????proxy.pentaKill();
????????proxy.victory();
????}
}
這里我們沒有創(chuàng)建代理類,也沒有實(shí)現(xiàn) ILOLPlayer 接口,但我們還是讓代練在幫我們上分,這就是動(dòng)態(tài)代理。
接下來看下 CGLIB 代理的方式,修改前面的代理類:
public?class?CglibProxy?implements?MethodInterceptor?{
????private?Object?target;
????public?Object?getInstance(final?Object?target)?{
????????this.target?=?target;
????????Enhancer?enhancer?=?new?Enhancer();
????????enhancer.setSuperclass(this.target.getClass());
????????enhancer.setCallback(this);
????????return?enhancer.create();
????}
????@Override
????public?Object?intercept(Object?o,?Method?method,?Object[]?objects,?MethodProxy?methodProxy)?throws?Throwable?{
????????Object?result?=?methodProxy.invoke(this.target,?objects);
????????return?result;
????}
}
編寫新的測試類:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????ILOLPlayer?ilolPlayer?=?new?LOLPlayer("geekdigging");
????????CglibProxy?proxy?=?new?CglibProxy();
????????LOLPlayer?lolPlayer?=?(LOLPlayer)?proxy.getInstance(ilolPlayer);
????????lolPlayer.login("geekdigging",?"password");
????????lolPlayer.pentaKill();
????????lolPlayer.victory();
????}
}
這里有一點(diǎn)需要注意, CGLIB 動(dòng)態(tài)代理需要具體對(duì)象擁有無參構(gòu)造,需要我們手動(dòng)在 LOLPlayer 中添加一個(gè)無參構(gòu)造函數(shù)。

