《設(shè)計(jì)模式之禪》讀書筆記:代理模式

1. 代理模式的定義和應(yīng)用

1.1 代理模式的定義

Provide a surrogate or placeholder for another object to control access to it .(為其他對象提供一種代理以控制對這個(gè)對象的訪問)

代理模式的類圖如下:

代理模式也叫做委托模式,是一種基本設(shè)計(jì)技巧。許多其他的模式,如狀態(tài)模式、策略模式、訪問者模式本質(zhì)上是在更特殊的場合采用了委托模式,而且在日常的應(yīng)用中,代理模式可以提供非常好的訪問控制。

代理模式中的三個(gè)角色的定義:

Subject 抽象主題角色

抽象主題類可以是抽象類也可以是接口,是一個(gè)最普通的業(yè)務(wù)類型定義,無特殊要求。

RealSubject

也叫做被委托角色、被代理角色。是業(yè)務(wù)邏輯的具體執(zhí)行者。

Proxy

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

Subject抽象主題類的通用源碼:

  1. 抽象主題類
public interface Subject{
  //定義一個(gè)方法
  public void request();
}
  1. 真實(shí)主題類
public class RealSubject implements Subject{
  //實(shí)現(xiàn)方法
  public void request(){
    //業(yè)務(wù)邏輯
  }
}
  1. 代理類
public class Proxy implements Subject{
  //要代理哪個(gè)實(shí)現(xiàn)類
  private Subject subject=null;
  //默認(rèn)被代理者
  public Proxy(){
    this.subject=new Proxy();
  }
  //通過構(gòu)造函數(shù)傳遞代理者
  public Proxy(Object ..objects){
   
  }
  //實(shí)現(xiàn)接口中定義的方法
  public void request(){
    this.before();
    this.subject.request();
    this.after();
  }
  //預(yù)處理
  private void before(){
    //do something
  }
  //善后處理
  private void after(){
    //do something
  }
}

一個(gè)代理類可以代理多個(gè)被委托者或被代理者,因此一個(gè)代理類具體代理哪個(gè)真實(shí)主題角色,是由場景類決定的。

1.2 代理模式的應(yīng)用

代理模式的優(yōu)點(diǎn):
  1. 職責(zé)清晰

    真實(shí)的角色就是實(shí)現(xiàn)實(shí)際的業(yè)務(wù)邏輯,不用關(guān)心其他非本職責(zé)的事務(wù),通過后期的代理完成一件事務(wù),附帶的結(jié)果就是編程簡潔清晰。

  2. 高擴(kuò)展性

    具體主題角色是隨時(shí)都會(huì)發(fā)生變化的,只要它實(shí)現(xiàn)了接口,代理類就能在不做任何修改的情況下使用。

  3. 智能化

代理模式的使用場景:

Spring 中的AOP

2.代理模式的擴(kuò)展

2.1普通代理

普通代理要求客戶端只能訪問代理角色,而不能訪問真實(shí)角色。


抽象游戲者
/**
 * @Title: IGamePlayer
 * @description: 
 * @author: gaoyakang
 * @date: 2018年1月22日 下午10:31:09
 * 
 */
public interface IGamePlayer {
    public void login(String user,String password);
    public void killBoss();
    public void upgrade();
}

普通代理的游戲者
/**
 * @Title: GamePlayer
 * @description: 
 * @author: gaoyakang
 * @date: 2018年1月22日 下午10:32:13
 * 
 */
public class GamePlayer implements IGamePlayer {
    private String name="";
    public GamePlayer(IGamePlayer _gamePlayer,String _name) throws Exception {
        if(_gamePlayer==null) {
            throw new Exception("不能創(chuàng)建真實(shí)角色!");
        }else {
            this.name=_name;
        }
    }

    public void login(String user, String password) {
        System.out.println("登錄名為"+user+"的用戶"+this.name+"登錄成功!");
    }

    public void killBoss() {
        System.out.println(this.name+"在打怪!");
    }

    public void upgrade() {
        System.out.println(this.name+"又升了一級!");
    }

}


普通代理的代理者
/**
 * @Title: GamePlayerProxy
 * @description: 
 * @author: gaoyakang
 * @date: 2018年1月22日 下午10:39:45
 * 
 */
public class GamePlayerProxy implements IGamePlayer{
    private IGamePlayer gamePlayer=null;
    public GamePlayerProxy(String name) {
        try {
            gamePlayer=new GamePlayer(this,name);
        }catch (Exception e) {
            // TODO: handle exception
        }
    }

    public void login(String user, String password) {
        this.gamePlayer.login(user, password);
    }

    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    public void upgrade() {
        this.gamePlayer.upgrade();
    }

}

場景類
import java.util.Date;
/**
 * @Title: Client
 * @description: 
 * @author: gaoyakang
 * @date: 2018年1月23日 下午10:51:09
 * 
 */
public class Client {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        IGamePlayer proxy=new GamePlayerProxy("張三");
        System.out.println("開始時(shí)間是:"+new Date());
        proxy.login("zhangsan", "admin");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("結(jié)束時(shí)間是:"+ new Date());
    }
}

與通用代理模式不同地方在于,普通代理在使用時(shí)代理角色只需要傳入代理者的名字,無需創(chuàng)建對象。換句話說,被代理人對象需要在代理者類中創(chuàng)建,也就實(shí)現(xiàn)了被代理人必須知道代理人的存在。實(shí)際項(xiàng)目中一般通過約定禁止new一個(gè)真實(shí)的角色來達(dá)到以上目的。

2.2 強(qiáng)制代理

強(qiáng)制代理類要求必須通過真實(shí)角色找到代理角色。

直觀的感受是,強(qiáng)制代理需要你先創(chuàng)建一個(gè)真實(shí)角色player并通過player的getProxy()方法來獲取代理。由于代理類和真實(shí)角色都繼承自IGamePlayer,所以代理類和真實(shí)角色都可以再獲取他們各自的代理。

接口類
public interface IGamePlayer {
    public void login(String user,String password);
    public void killBoss();
    public void upgrade();
    public IGamePlayer getProxy();
}

真實(shí)角色類
public class GamePlayer implements IGamePlayer {
    private String name = "";
    private IGamePlayer proxy = null;

    public GamePlayer(String _name) {
        this.name = _name;
    }

    @Override
    public void login(String user, String password) {
        if (isProxy()) {
            System.out.println("登錄名為" + user + "的用戶" + this.name + "登錄成功!");
        } else {
            System.out.println("請使用指定的代理訪問");
        }

    }

    @Override
    public void killBoss() {
        if (isProxy()) {
            System.out.println(this.name + "在打怪!");
        } else {
            System.out.println("請使用指定的代理訪問");
        }
    }

    @Override
    public void upgrade() {
        if (isProxy()) {
            System.out.println(this.name + "又升了一級!");
        } else {
            System.out.println("請使用指定的代理訪問");
        }
    }

    @Override
    public IGamePlayer getProxy() {
        this.proxy = new GamePlayerProxy(this);
        return this.proxy;
    }

    private boolean isProxy() {
        if (this.proxy == null) {
            return false;
        } else
            return true;
    }

}

代理類
public class GamePlayerProxy implements IGamePlayer{
    private IGamePlayer gamePlayer=null;
    public GamePlayerProxy(IGamePlayer _gamePlayer) {
        this.gamePlayer=_gamePlayer;
    }

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

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

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

    @Override
    public IGamePlayer getProxy() {
        return this;
    }

}

具體使用的時(shí)候,代理的獲取方式跟普通代理類有些不同。如果你直接new一個(gè)代理類,那么是無法獲取到正確的操作的,你必須用真實(shí)角色的getProxy()方法來獲取它的代理。這就是指定的代理。

        IGamePlayer player=new GamePlayer("張三");
        IGamePlayer proxy=player.getProxy();

完整的場景類代碼如下:

場景類
public class Client {
    public static void main(String[] args) {
        IGamePlayer player=new GamePlayer("張三");
        IGamePlayer proxy=player.getProxy();
        System.out.println("開始時(shí)間是:"+new Date());
        proxy.login("zhangsan", "admin");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("結(jié)束時(shí)間是:"+ new Date());
    }
}

2.3 代理是有個(gè)性的

上面的例子中,代理類實(shí)現(xiàn)的接口跟真實(shí)角色是一樣的。代理這個(gè)名字,就意味著我們可以通過代理實(shí)現(xiàn)我們在真實(shí)角色類中無法或者不想實(shí)現(xiàn)的方法。因此代理類應(yīng)該實(shí)現(xiàn)跟真實(shí)角色類不同的功能,這就是代理的個(gè)性之處。

代理的目的是在目標(biāo)對象方法的基礎(chǔ)上做增強(qiáng),這種增強(qiáng)的本質(zhì)通常就是對目標(biāo)對象的方法進(jìn)行過濾和攔截。

按書中的例子我們實(shí)現(xiàn)一個(gè)代理收費(fèi)的功能。

類圖如下:

以強(qiáng)制代理類為基礎(chǔ),我們實(shí)現(xiàn)上述功能。

代理類的接口
public interface IProxy {
    public void count();
}
實(shí)現(xiàn)了收費(fèi)功能的代理類
public class GamePlayerProxy implements IGamePlayer,IProxy{
    private IGamePlayer gamePlayer=null;
    public GamePlayerProxy(IGamePlayer _gamePlayer) {
        this.gamePlayer=_gamePlayer;
    }

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

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

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

    @Override
    public IGamePlayer getProxy() {
        return this;
    }

    @Override
    public void count() {
        System.out.println("升級費(fèi)用150元");     
    }

}
//運(yùn)行結(jié)果
//開始時(shí)間是:Tue Jan 30 22:58:14 CST 2018
//登錄名為zhangsan的用戶張三登錄成功!
//張三在打怪!
//張三又升了一級!
//升級費(fèi)用150元
//結(jié)束時(shí)間是:Tue Jan 30 22:58:14 CST 2018

真實(shí)角色類和Client端都無需任何改動(dòng),運(yùn)行Client之后會(huì)發(fā)現(xiàn)果然實(shí)現(xiàn)了收費(fèi)的功能。

看到這里我想到了AOP。我們知道AOP的主要作用就是不改動(dòng)現(xiàn)有代碼而為其增加功能,如打印日志。上面代碼實(shí)現(xiàn)收費(fèi)功能時(shí)并沒有改動(dòng)真實(shí)角色,但是我們依然實(shí)現(xiàn)了增加功能的效果??雌饋硎怯悬c(diǎn)AOP的影子,但是我們都知道Spring 中的AOP,使用的時(shí)候并不是在寫代碼的時(shí)候單獨(dú)寫代理類,而是在運(yùn)行的過程中才指定代理去實(shí)現(xiàn)功能。AOP就是這樣定義的:

這種在運(yùn)行時(shí),動(dòng)態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。

那么如何實(shí)現(xiàn)動(dòng)態(tài)的代理呢?下面我們將會(huì)了解代理模式中最重要的一個(gè)擴(kuò)展——?jiǎng)討B(tài)代理。

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

動(dòng)態(tài)代理是在實(shí)現(xiàn)階段不用關(guān)心代理誰,而在運(yùn)行階段才指定代理哪一個(gè)對象

動(dòng)態(tài)代理類圖中增加了一個(gè)InvocationHandler接口和GamePlayIH類,作用就是產(chǎn)生一個(gè)對象的代理對象,其中InvocationHandler是JDK提供的動(dòng)態(tài)代理接口,對被代理類的方法進(jìn)行代理。

動(dòng)態(tài)代理類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class GamePlayIH implements InvocationHandler{

    Class cls=null;
    Object obj=null;
    public GamePlayIH(Object _obj) {
        this.obj=_obj;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result=method.invoke(this.obj, args);
        if(method.getName().equalsIgnoreCase("login")) {
            System.out.println("有人在用我的賬號登錄!");
        }
        return result;
    }
}

其中invoke 方法時(shí)接口InvocationHandler定義必須實(shí)現(xiàn)的,它完成對真實(shí)方法的調(diào)用。動(dòng)態(tài)代理根據(jù)被代理的接口生成所有的方法,也就是說給定一個(gè)接口,動(dòng)態(tài)代理會(huì)宣稱“我已經(jīng)實(shí)現(xiàn)該接口下的所有方法了”,那么動(dòng)態(tài)代理怎么才能實(shí)現(xiàn)被代理接口中的方法呢?通過InvocationHandler接口,所有方法都有該Handler來進(jìn)行處理,也就是所有被代理的方法都由InvocationHandler接管實(shí)際的處理任務(wù)。

我們在動(dòng)態(tài)代理類中增加了一個(gè)檢驗(yàn)登錄的功能,這樣更能直觀的體會(huì)到動(dòng)態(tài)代理的好處。

動(dòng)態(tài)代理的場景類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Date;
public class Client {
    public static void main(String[] args) {
        IGamePlayer player=new GamePlayer("張三");
        InvocationHandler handler=new GamePlayIH(player);
        ClassLoader c1=player.getClass().getClassLoader();
        IGamePlayer proxy=(IGamePlayer)Proxy.newProxyInstance(c1, new Class[] {IGamePlayer.class}, handler);
        System.out.println("開始時(shí)間是:"+new Date());
        proxy.login("zhangsan", "admin");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("結(jié)束時(shí)間是:"+ new Date());
    }
}

//output:
//開始時(shí)間是:Fri Feb 02 23:22:04 CST 2018
//登錄名為zhangsan的用戶張三登錄成功!
//有人在用我的賬號登錄!
//張三在打怪!
//張三又升了一級!
//結(jié)束時(shí)間是:Fri Feb 02 23:22:04 CST 2018

2.5 動(dòng)態(tài)代理類的通用模型

動(dòng)態(tài)代理類的通用類圖如下:

動(dòng)態(tài)代理實(shí)現(xiàn)代理的職責(zé),業(yè)務(wù)邏輯Subject 實(shí)現(xiàn)相關(guān)的邏輯功能,兩者之間沒有必然的相互耦合的關(guān)系。通知Adivice 從另一個(gè)切面切入,最終在高層模塊Client進(jìn)行耦合,完成邏輯的封裝任務(wù)。

抽象主題
public interface Subject {
    public void doSomething(String str);
}
真實(shí)主題
public class RealSubject implements Subject{
    public void doSomething(String str) {
        System.out.println("do something!---->"+str);       
    }
}
動(dòng)態(tài)代理的Handler類
public class MyInvocationHandler implements InvocationHandler{
    //被代理的對象
    private Object target=null;
    public MyInvocationHandler(Object _obj) {
        this.target=_obj;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.target, args);
    }
}
通知接口及實(shí)現(xiàn)
public interface IAdvice {
    public void exec();
}
public class BeforeAdvice implements IAdvice{
    public void exec() {
        System.out.println("我是前置通知,我被執(zhí)行了!");
    }
}
動(dòng)態(tài)代理類
public class DynamicProxy<T> {
    public static <T> T newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {
        if(true) {
            (new BeforeAdvice()).exec();
        }
        return (T)Proxy.newProxyInstance(loader, interfaces, h);
    }
}

具體業(yè)務(wù)的動(dòng)態(tài)代理
public class SubjectDynamicProxy extends DynamicProxy{
    public static <T> T newProxyInstance(Subject subject) {
        ClassLoader loader=subject.getClass().getClassLoader();
        Class<?>[] classes=subject.getClass().getInterfaces();
        InvocationHandler handler=new MyInvocationHandler(subject);
        return newProxyInstance(loader, classes, handler);
    }
}
動(dòng)態(tài)代理的場景類
public class Client {
    public static void main(String[] args) {
        Subject subject=new RealSubject();
        Subject proxy=SubjectDynamicProxy.newProxyInstance(subject);
        proxy.doSomething("Finish");
    }
}

如果你對Java中的反射有所了解的話,上面的代碼理解起來應(yīng)該不難。我們知道,反射就是把java類中的各種成分映射成一個(gè)個(gè)的Java對象。Java在運(yùn)行時(shí)會(huì)生成.class文件,反射的作用就是在程序運(yùn)行時(shí)動(dòng)態(tài)得.class文件中解析出類中的對象和方法。所以要實(shí)現(xiàn)在不修改現(xiàn)有代碼的基礎(chǔ)上添加功能我們肯定應(yīng)該想到用反射去實(shí)現(xiàn)。

3.代理模式的應(yīng)用

代理模式應(yīng)用十分廣泛,其中最貼近我們的就是AOP了,我會(huì)在以后的文章中詳細(xì)分析AOP。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容