設(shè)計(jì)模式與范式 --- 結(jié)構(gòu)型模式(代理模式)

1.寫(xiě)在前

代理模式(Proxy): 為其他對(duì)象提供一個(gè)代理,用來(lái)控制這個(gè)對(duì)象的訪問(wèn)。

解讀:使用代理模式以后,客戶端直接訪問(wèn)代理,代理在客戶端和目標(biāo)對(duì)象之間起到中介的作用。

案例分析

當(dāng)我們開(kāi)發(fā)了一個(gè) MetricsCollector 類,用來(lái)收集接口請(qǐng)求的原始數(shù)據(jù),比如訪問(wèn)時(shí)間、處理時(shí)長(zhǎng)等。具體代碼如下:

當(dāng)我們開(kāi)發(fā)了一個(gè) MetricsCollector 類,用來(lái)收集接口請(qǐng)求的原始數(shù)據(jù),比如訪問(wèn)時(shí)間、處理時(shí)長(zhǎng)等。具體代碼如下:

public class UserController {
  //...省略其他屬性和方法...
  private MetricsCollector metricsCollector; //依賴注入
  public UserVo login(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();
    // ... 省略login邏輯...
    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
      
    RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);
    //...返回UserVo數(shù)據(jù)...
  }
    
  public UserVo register(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();
    // ... 省略register邏輯...
    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
      
    RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);
    //...返回UserVo數(shù)據(jù)...
  }
}

上述代碼存在的問(wèn)題:非業(yè)務(wù)代碼和業(yè)務(wù)代碼耦合!

  • 第一,性能計(jì)數(shù)器框架代碼侵入到業(yè)務(wù)代碼中,跟業(yè)務(wù)代碼高度耦合。

  • 第二,收集接口請(qǐng)求的代碼跟業(yè)務(wù)代碼無(wú)關(guān),本就不應(yīng)該放到一個(gè)類中。

2.重構(gòu)方案

(1)優(yōu)化方案1:原始類和代理類實(shí)現(xiàn)相同的接口

為了將框架代碼和業(yè)務(wù)代碼解耦,代理模式就派上用場(chǎng)了。

具體操作:代理類 UserControllerProxy 和原始類 UserController 實(shí)現(xiàn)相同的接口 IUserController。UserController 類只負(fù)責(zé)業(yè)務(wù)功能。代理類 UserControllerProxy 負(fù)責(zé)在業(yè)務(wù)代碼執(zhí)行前后附加其他邏輯代碼,并通過(guò)委托的方式調(diào)用原始類來(lái)執(zhí)行業(yè)務(wù)代碼。

具體操作:代理類 UserControllerProxy 和原始類 UserController 實(shí)現(xiàn)相同的接口 IUserController。UserController 類只負(fù)責(zé)業(yè)務(wù)功能。代理類 UserControllerProxy 負(fù)責(zé)在業(yè)務(wù)代碼執(zhí)行前后附加其他邏輯代碼,并通過(guò)委托的方式調(diào)用原始類來(lái)執(zhí)行業(yè)務(wù)代碼。

//1.抽象角色:抽象主題類的主要職責(zé)是生命真實(shí)主題和代理的共同接口方法,該類可以是接口,也可以是抽象類。
public interface IUserController {
  UserVo login(String telephone, String password);
  UserVo register(String telephone, String password);
}

//2.原始類/被代理類(RealSubject):定義了代理所表示的真實(shí)對(duì)象,只負(fù)責(zé)系統(tǒng)中的真正的業(yè)務(wù)邏輯對(duì)象
public class UserController implements IUserController {
  //...省略其他屬性和方法...
  @Override
  public UserVo login(String telephone, String password) {
    //...省略login邏輯...
    //...返回UserVo數(shù)據(jù)...
  }
  @Override
  public UserVo register(String telephone, String password) {
    //...省略register邏輯...
    //...返回UserVo數(shù)據(jù)...
  }
}

//3.代理類:具有RealSubject的引用,具備完全的對(duì)RealSubject的代理權(quán),客戶端調(diào)用代理對(duì)象的方法,也調(diào)用被代理對(duì)象的方法,但是會(huì)在代理對(duì)象前后增加一些邏輯代碼。
public class UserControllerProxy implements IUserController {
  private MetricsCollector metricsCollector;
  private UserController userController;
  public UserControllerProxy(UserController userController) {
    this.userController = userController;
    this.metricsCollector = new MetricsCollector();
  }
  @Override
  public UserVo login(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();
    // 委托處理
    UserVo userVo = userController.login(telephone, password);
    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);
    return userVo;
  }
  @Override
  public UserVo register(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();
    UserVo userVo = userController.register(telephone, password);
    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);
    return userVo;
  }
}

//UserControllerProxy使用舉例
//因?yàn)樵碱惡痛眍悓?shí)現(xiàn)相同的接口,是基于接口而非實(shí)現(xiàn)編程?。?!
//將UserController類對(duì)象替換為UserControllerProxy類對(duì)象,不需要改動(dòng)太多代碼
IUserController userController = new UserControllerProxy(new UserController());

(2)優(yōu)化方案2:代理類繼承原始類

如果如果原始類并沒(méi)有定義接口,并且原始類代碼并不是我們開(kāi)發(fā)維護(hù)的(比如它來(lái)自一個(gè)第三方的類庫(kù)),我們也沒(méi)辦法直接修改原始類,給它重新定義一個(gè)接口。

具體操作:對(duì)于這種外部類的擴(kuò)展,我們一般都是采用繼承的方式。我們讓代理類繼承原始類,然后擴(kuò)展附加功能。

// 代理類繼承原始類
public class UserControllerProxy extends UserController {
  private MetricsCollector metricsCollector;
  public UserControllerProxy() {
    this.metricsCollector = new MetricsCollector();
  }
    
  public UserVo login(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();
    UserVo userVo = super.login(telephone, password);
    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);
    return userVo;
  }
    
  public UserVo register(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();
    UserVo userVo = super.register(telephone, password);
    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);
    return userVo;
  }
}

//UserControllerProxy使用舉例
UserController userController = new UserControllerProxy();

上文的代碼實(shí)現(xiàn)還是有點(diǎn)問(wèn)題:

  • 一方面,我們需要在代理類中,將原始類中的所有的方法,都重新實(shí)現(xiàn)一遍,并且為每個(gè)方法都附加相似的代碼邏輯。
  • 另一方面,如果要添加的附加功能的類有不止一個(gè)(這里只包括登錄注冊(cè)功能),我們需要針對(duì)每個(gè)類都創(chuàng)建一個(gè)代理類。
    (3)優(yōu)化方案3:動(dòng)態(tài)代理原理解析

動(dòng)態(tài)代理(Dynamic Proxy),就是我們不事先為每個(gè)原始類編寫(xiě)代理類,而是在運(yùn)行的時(shí)候,動(dòng)態(tài)地創(chuàng)建原始類對(duì)應(yīng)的代理類,然后在系統(tǒng)中用代理類替換掉原始類。實(shí)際上,動(dòng)態(tài)代理底層依賴的就是java的反射語(yǔ)法。

public class MetricsCollectorProxy {
  private MetricsCollector metricsCollector;
  public MetricsCollectorProxy() {
    this.metricsCollector = new MetricsCollector();
  }
  
  // 動(dòng)態(tài)創(chuàng)建代理
  public Object createProxy(Object proxiedObject) {
    Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
    DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
    return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
  }
    
  private class DynamicProxyHandler implements InvocationHandler {
    private Object proxiedObject;
    public DynamicProxyHandler(Object proxiedObject) {
      this.proxiedObject = proxiedObject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      long startTimestamp = System.currentTimeMillis();
      Object result = method.invoke(proxiedObject, args);
      long endTimeStamp = System.currentTimeMillis();
      long responseTime = endTimeStamp - startTimestamp;
      String apiName = proxiedObject.getClass().getName() + ":" + method.getName();
      RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp);
      metricsCollector.recordRequest(requestInfo);
      return result;
    }
  }
}
//MetricsCollectorProxy使用舉例
MetricsCollectorProxy proxy = new MetricsCollectorProxy();
IUserController userController = (IUserController) proxy.createProxy(new UserController());

3.應(yīng)用場(chǎng)景

(1)業(yè)務(wù)系統(tǒng)的非功能性開(kāi)發(fā)

  • 比如:監(jiān)控、統(tǒng)計(jì)、鑒權(quán)、限流、事務(wù)、冪等、日志。我們將這些附加功能(非功能性開(kāi)發(fā))與業(yè)務(wù)功能解耦,放到代理類中統(tǒng)一處理,讓程序員只需要關(guān)注業(yè)務(wù)方面的開(kāi)發(fā)。

(2)在RPC和緩存中應(yīng)用

  • 實(shí)際上,RPC框架也可以看作一種代理模式,GoF 的《設(shè)計(jì)模式》一書(shū)中把它稱作遠(yuǎn)程代理。通過(guò)遠(yuǎn)程代理,將網(wǎng)絡(luò)通信、數(shù)據(jù)編解碼等細(xì)節(jié)隱藏起來(lái)??蛻舳嗽谑褂?RPC 服務(wù)的時(shí)候,就像使用本地函數(shù)一樣,無(wú)需了解跟服務(wù)器交互的細(xì)節(jié)。

  • 在緩存中的應(yīng)用:

    • 假設(shè)我們要開(kāi)發(fā)一個(gè)接口請(qǐng)求的緩存功能,對(duì)于某些接口請(qǐng)求,如果入?yún)⑾嗤谠O(shè)定的過(guò)期時(shí)間內(nèi),直接返回緩存結(jié)果,而不用重新進(jìn)行邏輯處理。

    • 在應(yīng)用啟動(dòng)的時(shí)候,我們從配置文件中加載需要支持緩存的接口,以及相應(yīng)的緩存策略(比如過(guò)期時(shí)間)等。當(dāng)請(qǐng)求到來(lái)的時(shí)候,我們?cè)?AOP 切面中攔截請(qǐng)求,如果請(qǐng)求中帶有支持緩存的字段(比如 http://…?..&cached=true),我們便從緩存(內(nèi)存緩存或者 Redis 緩存等)中獲取數(shù)據(jù)直接返回。

4.總結(jié)與補(bǔ)充

代理模式的優(yōu)點(diǎn)降低了系統(tǒng)的耦合性,將框架代碼(非業(yè)務(wù)代碼/附加功能)和業(yè)務(wù)代碼解耦。

  • 職責(zé)清晰

  • 擴(kuò)展性高

缺點(diǎn):由于多了一個(gè)代理對(duì)象,可能會(huì)使請(qǐng)求的處理速度變慢。有些代理的實(shí)現(xiàn)較為復(fù)雜。

補(bǔ)充通過(guò)對(duì)訪問(wèn)的代理,我們可以用遠(yuǎn)程代理、虛擬代理、安全代理等,來(lái)減少直接對(duì)對(duì)象訪問(wèn)產(chǎn)生的開(kāi)銷,即不同類型的代理可以對(duì)客戶端對(duì)對(duì)象的訪問(wèn)進(jìn)行不同的控制:

  • 遠(yuǎn)程代理:使得客戶端可以訪問(wèn)在遠(yuǎn)程機(jī)器上的對(duì)象,遠(yuǎn)程機(jī)器可能具有更好的計(jì)算性能與處理速度,可以快速響應(yīng)并處理客戶端請(qǐng)求。

  • 虛擬代理:通過(guò)使用一個(gè)小對(duì)象來(lái)代表一個(gè)大對(duì)象,可以減少系統(tǒng)資源的消耗,對(duì)系統(tǒng)進(jìn)行優(yōu)化并提高運(yùn)行速度。

  • 保護(hù)代理:可以控制客戶端對(duì)真實(shí)對(duì)象的使用權(quán)限。

5.參考

《設(shè)計(jì)模式之美》 --- 王爭(zhēng)

http://www.itdecent.cn/p/35918f6946a6

http://www.itdecent.cn/p/53388cb1e4e7

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

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

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