代理模式

解釋

為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問

示例引入

以打游戲?yàn)槔悠寐樱蟾耪麄€(gè)打游戲的過程可以概括為登錄泰佳、打怪、升級(jí)、砍人虱疏、被人砍等等一系列動(dòng)作。由于游戲打的時(shí)間長了阔馋,腰酸背痛扔罪、眼睛干澀、手臂麻木等等梭冠,其結(jié)果就類似于吃了那個(gè)“一日喪命散”辕狰,“筋脈逆流,胡思亂想妈嘹,而致走火入魔”柳琢,那怎么辦?我們想玩游戲润脸,但又不想碰觸到游戲中的煩惱柬脸,如何解決呢?有辦法毙驯,現(xiàn)在游戲代練的公司非常多倒堕,可以把自己的賬號(hào)交給代練人員,由他們?nèi)臀覀兩?jí)打怪爆价,非常好的想法垦巴,整個(gè)過程抽象成程序如下:

游戲類圖

游戲類圖
/**
 * Created by zs on 2017/3/23.
 *
 * 游戲者接口
 */
public interface IGamePlayer{
    //登錄
    public void login(String user,String password);

    //殺怪
    public void killBoss();

    //升級(jí)
    public void upgrade();
}

/**
 * Created by zs on 2017/3/23.
 *
 * 游戲者
 */
public class GamePlayer implements IGamePlayer {
    private String mName = "";
    public GamePlayer(String name){
        this.mName = name;
    }
    @Override
    public void login(String user, String password) {
        System.out.println("登錄名為" +user + "的用戶" + this.mName + "登錄成功媳搪!" );
    }

    @Override
    public void killBoss() {
        System.out.println(this.mName + "在打怪");
    }

    @Override
    public void upgrade() {
        System.out.println(this.mName + "又升了一級(jí)");
    }
}



/**
 * Created by zs on 2017/3/23.
 *
 * 代練者
 */
public class GamePlayerProxy implements IGamePlayer {

    private  IGamePlayer mGamePlayer = null;
    public GamePlayerProxy(IGamePlayer gamePlayer){
        this.mGamePlayer = gamePlayer;
    }

    @Override
    public void login(String user, String password) {
        this.mGamePlayer.login(user,password);
    }

    @Override
    public void killBoss() {
        this.mGamePlayer.killBoss();
    }

    @Override
    public void upgrade() {
        this.mGamePlayer.upgrade();
    }
}


/**
 * Created by zs on 2017/3/23.
 *
 * 場景類
 */
public class Client {
    public static void main(String[] args) {
        IGamePlayer player = new GamePlayer("張三");

        IGamePlayer proxy = new GamePlayerProxy(player);

        proxy.login("San","123456");

        proxy.killBoss();

        proxy.upgrade();
    }
}

通用類圖

代理模式通用類圖

類圖解析

Subject:抽象主題角色,可以是抽象類也可以是接口骤宣,是一個(gè)最普通的業(yè)務(wù)類型定義秦爆,無特殊要求。(目標(biāo)接口)

RealSubject:具體主題角色憔披,也叫做被委托角色等限、被代理角色、是業(yè)務(wù)邏輯的具體執(zhí)行者芬膝。(目標(biāo)類)

Proxy:代理主題角色望门,也叫委托類、代理類锰霜。它負(fù)責(zé)最真實(shí)角色的應(yīng)用筹误,把所有抽象主題類定義的方法限制委托給真實(shí)主題角色實(shí)現(xiàn),并且在真實(shí)主題角色處理完畢前后做預(yù)處理和善后處理工作(代理類)

寫法

靜態(tài)代理:目標(biāo)類和代理類實(shí)現(xiàn)或繼承目標(biāo)接口或抽象類癣缅,代理類中有目標(biāo)類的引用

動(dòng)態(tài)代理:見下文描述

通用代碼

/**
 * Created by zs on 2017/3/24.
 *
 * 抽象主題類
 */
public interface Subject {
    public void request();
}


/**
 * Created by zs on 2017/3/24.
 *
 * 真實(shí)主題類
 */
public class RealSubject implements Subject {
    @Override
    public void request() {
        // to do you work
    }
}


/**
 * Created by zs on 2017/3/24.
 *
 * 代理類
 */
public class Proxy implements Subject {

    private Subject mSubject = null;

    //默認(rèn)代理者
    public  Proxy(){
        this.mSubject = new Proxy();
    }

    //通過構(gòu)造函數(shù)傳遞代理者
    public Proxy(Subject subject){
        this.mSubject = subject;
    }

    @Override
    public void request() {
        this.before();
        this.mSubject.request();
        this.after();
    }

    //預(yù)處理
    private void before(){
        //to do you work...
    }

    //善后
    private void after(){
        // to do you work...
    }
}

代理模式分類

總的分為:靜態(tài)代理動(dòng)態(tài)代理

遠(yuǎn)程代理:隱藏了一個(gè)對(duì)象存在于不同的地址空間的事實(shí)厨剪,也即是客戶通過遠(yuǎn)程代理去訪問一個(gè)對(duì)象,根本就不關(guān)心這個(gè)對(duì)象在哪里所灸,也不關(guān)心如何通過網(wǎng)絡(luò)去訪問到這個(gè)對(duì)象丽惶,從客戶的角度來講,它只是在使用代理對(duì)象而已爬立。

虛擬代理:可以根據(jù)需要來創(chuàng)建“大”對(duì)象,只有到必須創(chuàng)建對(duì)象的時(shí)候侠驯,虛代理才會(huì)創(chuàng)建對(duì)象抡秆,從而大大加快程序運(yùn)行速度,并節(jié)省資源吟策。通過虛代理可以對(duì)系統(tǒng)進(jìn)行優(yōu)化儒士。

保護(hù)代理:可以在訪問一個(gè)對(duì)象的前后,執(zhí)行很多附加的操作檩坚,除了進(jìn)行權(quán)限控制之外着撩,還可以進(jìn)行很多跟業(yè)務(wù)相關(guān)的處理,而不需要修改被代理的對(duì)象匾委。也就是說拖叙,可以通過代理來給目標(biāo)對(duì)象增加功能。

智能引用代理:允許在訪問一個(gè)對(duì)象的前后赂乐,執(zhí)行很多附加的操作薯鳍,這樣一來就可以做很多額外的事情。(本文中重點(diǎn)解釋描述

應(yīng)用場景

需要為一個(gè)對(duì)象在不同的地址空間提供局部代表的時(shí)候挨措,可以使用遠(yuǎn)程代理挖滤;

需要按照需要?jiǎng)?chuàng)建開銷很大的對(duì)象的時(shí)候崩溪,可以使用虛擬代理

需要控制對(duì)原始對(duì)象的訪問的時(shí)候斩松,可以使用保護(hù)代理伶唯;

需要在訪問對(duì)象的時(shí)候執(zhí)行一些附加操作的時(shí)候,可以使用智能引用代理惧盹;(本文中重點(diǎn)解釋描述 )--->(重要的事說三遍

壓軸大戲之動(dòng)態(tài)代理

問題:

  1. 對(duì)于靜態(tài)代理而言抵怎,我們每一個(gè)代理類都只能為一個(gè)接口服務(wù),這樣一來程序開發(fā)必然會(huì)產(chǎn)生過多的代理
  2. 如果Subject接口發(fā)生變化岭参,那么代理類和具體的目標(biāo)實(shí)現(xiàn)都要變化,不是很靈活

解決這些問題的最好的做法是可以通過一個(gè)代理類完成全部的代理功能尝艘,那么此時(shí)就必須使用動(dòng)態(tài)代理完成⊙莺睿現(xiàn)在有一個(gè)非常流行的名稱叫做面向切面編程,也就是AOP背亥,其核心就是采用了動(dòng)態(tài)代理機(jī)制秒际。

對(duì)于動(dòng)態(tài)代理jdk和cglib都已經(jīng)做了實(shí)現(xiàn),我們?cè)陂_發(fā)中使用它們封裝好的動(dòng)態(tài)代理很方便狡汉。(本文重點(diǎn)描述jdk是如何實(shí)現(xiàn)動(dòng)態(tài)代理)Java對(duì)代理模式提供了內(nèi)建的支持娄徊,在java.lang.reflect包下面,提供了一個(gè)Proxy的類和一個(gè)InvocationHandler的接口盾戴。

示例代碼

/**
 * Created by zs on 2017/3/23.
 *
 * 游戲者接口
 */
public interface IGamePlayer {
    //登錄
    public void login(String user, String password);

    //殺怪
    public void killBoss();

    //升級(jí)
    public void upgrade();
}


/**
 * Created by zs on 2017/3/23.
 *
 * 游戲者
 */
public class GamePlayer implements IGamePlayer {
    private String mName = "";
    public GamePlayer(String name){
        this.mName = name;
    }
    @Override
    public void login(String user, String password) {
        System.out.println("登錄名為" +user + "的用戶" + this.mName + "登錄成功寄锐!" );
    }

    @Override
    public void killBoss() {
        System.out.println(this.mName + "在打怪");
    }

    @Override
    public void upgrade() {
        System.out.println(this.mName + "又升了一級(jí)");
    }
}


/**
 * Created by zs on 2017/3/24.
 *
 * 動(dòng)態(tài)代理類
 * 動(dòng)態(tài)代理是根據(jù)被代理的接口生成所有的方法,
 * 也就是說給定一個(gè)接口尖啡,動(dòng)態(tài)代理會(huì)宣稱"我已經(jīng)實(shí)現(xiàn)了該接口的所有方法了"
 */
public class GamePlayerIH implements InvocationHandler{

    //被代理者
    Class cls = null;

    //被代理實(shí)例
    Object obj = null;

    //我要代理誰
    public GamePlayerIH(Object obj){
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(this.obj,args);
        return result;
    }
}


/**
 * Created by zs on 2017/3/23.
 *
 * 場景類
 */
public class Client {
    public static void main(String[] args) {

        //定義一個(gè)玩家
        IGamePlayer player = new GamePlayer("張三");

        //定義一個(gè)handler
        InvocationHandler handler = new GamePlayerIH2(player);

        //獲得類的class loader
        ClassLoader cl = player.getClass().getClassLoader();

        //動(dòng)態(tài)產(chǎn)生一個(gè)代理者
        IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(cl,new Class[]{IGamePlayer.class},handler);

        //登錄
        proxy.login("San","123456");

        //殺怪
        proxy.killBoss();

        //升級(jí)
        proxy.upgrade();
    }
}

動(dòng)態(tài)代理類圖

動(dòng)態(tài)代理類圖

相關(guān)概念

切面:例如:在用戶注冊(cè)時(shí)候橄仆,上傳用戶頭像、獲取表單數(shù)據(jù)衅斩、權(quán)限驗(yàn)證等就是一個(gè)個(gè)切面
通知:切面中的方法(前置通知盆顾、后置通知、環(huán)繞通知畏梆、最終通知您宪、異常通知等)
切入點(diǎn):只有符合切入點(diǎn),才能讓通知和目標(biāo)方法結(jié)合在一起
織入:形成代理對(duì)象的方法的過程

動(dòng)態(tài)代理通用代碼

/**
 * Created by zs on 2017/3/24.
 *
 * 抽象主題
 */
public interface Subject {
    //業(yè)務(wù)處理
    public void doSomething(String string);
}


**
 * Created by zs on 2017/3/24.
 *
 * 真實(shí)主題
 */
public class RealSubject implements Subject {
    @Override
    public void doSomething(String string) {
        System.out.println("do something ---> " + string);
    }
}


/**
 * Created by zs on 2017/3/24.
 *
 * 動(dòng)態(tài)代理的Handler類
 */
public class MyInvocationHandler implements InvocationHandler {
    //被代理的對(duì)象
    private Object target = null;

    //通過構(gòu)造函數(shù)傳遞一個(gè)對(duì)象
    public MyInvocationHandler(Object object){
        this.target = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.target,args);
    }
}


/**
 * Created by zs on 2017/3/24.
 *
 * 通知
 */
public interface IAdvice {
    public void execute();
}


/**
 * Created by zs on 2017/3/24.
 *
 * 前置通知
 */
public class BeforeAdvice implements IAdvice {
    @Override
    public void execute() {
        System.out.println("我是前置通知奠涌,我被執(zhí)行了");
    }
}

/**
 * Created by zs on 2017/3/24.
 *
 * 動(dòng)態(tài)代理類
 */
public class DynamicProxy<T> {
    public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler){

        //尋找JoinPoint切入點(diǎn)
        if(true){
            (new BeforeAdvice()).execute();
        }
        //執(zhí)行目標(biāo)宪巨,并返回結(jié)果
        return (T)Proxy.newProxyInstance(loader,interfaces,invocationHandler);
    }

}



/**
 * Created by zs on 2017/3/24.
 *
 * 場景類
 */
public class Client {
    public static void main(String[] args) {
        Subject subject = new RealSubject();

        InvocationHandler handler = new MyInvocationHandler(subject);

        Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);

        proxy.doSomething("Finish");
    }
}

代碼賞析

Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);

該方法是重新生成了一個(gè)對(duì)象,subject.getClass().getInterfaces():查找到該類的所有接口铣猩,然后實(shí)現(xiàn)接口的所有方法揖铜,最終由new MyInvocationHandler(subject)這個(gè)對(duì)象接管。

代理模式示意圖

代理模式示意圖

jdk動(dòng)態(tài)代理與cglib區(qū)別

Java的動(dòng)態(tài)代理目前只能代理接口达皿,基本的實(shí)現(xiàn)是依靠Java的反射機(jī)制和動(dòng)態(tài)生成class的技術(shù)天吓,來動(dòng)態(tài)生成被代理的接口的實(shí)現(xiàn)對(duì)象贿肩。如果要實(shí)現(xiàn)類的代理,可以使用cglib(一個(gè)開源的Code Generation Library)

效果

  1. 職責(zé)清晰:真實(shí)的角色不用關(guān)心其他非本職工作的事務(wù)龄寞,通過后期的代理完成一件事務(wù)汰规,附帶的結(jié)果就是編程簡介清晰
  2. 高擴(kuò)展性:具體主題角色是隨時(shí)都會(huì)變化,只要它實(shí)現(xiàn)了接口物邑,甭管它如何變化溜哮,都逃不開如來佛的手掌(接口)
  3. 智能化:主要用來做方法的增強(qiáng),讓你可以在不修改源碼的情況下色解,增強(qiáng)一些方法茂嗓,在方法執(zhí)行前后做任何你想做的事情
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市科阎,隨后出現(xiàn)的幾起案子述吸,更是在濱河造成了極大的恐慌,老刑警劉巖锣笨,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝌矛,死亡現(xiàn)場離奇詭異,居然都是意外死亡错英,警方通過查閱死者的電腦和手機(jī)入撒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椭岩,“玉大人茅逮,你說我怎么就攤上這事〔净停” “怎么了氮唯?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長姨伟。 經(jīng)常有香客問我惩琉,道長,這世上最難降的妖魔是什么夺荒? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任瞒渠,我火速辦了婚禮,結(jié)果婚禮上技扼,老公的妹妹穿的比我還像新娘伍玖。我一直安慰自己,他們只是感情好剿吻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布窍箍。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪椰棘。 梳的紋絲不亂的頭發(fā)上纺棺,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音邪狞,去河邊找鬼祷蝌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛帆卓,可吹牛的內(nèi)容都是我干的巨朦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼剑令,長吁一口氣:“原來是場噩夢啊……” “哼糊啡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吁津,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤悔橄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后腺毫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挣柬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年潮酒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邪蛔。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡急黎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侧到,到底是詐尸還是另有隱情勃教,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布匠抗,位于F島的核電站故源,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏汞贸。R本人自食惡果不足惜绳军,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望矢腻。 院中可真熱鬧门驾,春花似錦、人聲如沸多柑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至聂沙,卻和暖如春秆麸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逐纬。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工蛔屹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人豁生。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓兔毒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親甸箱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子育叁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容