架構師內功心法:設計模式(三)結構型模式(一)

寫在前面:

  • 你好,歡迎你的閱讀!
  • 我熱愛技術,熱愛分享,熱愛生活, 我始終相信:技術是開源的,知識是共享的!
  • 博客裏面的內容大部分均爲原創,是自己日常的學習記錄和總結,便於自己在後面的時間裏回顧,當然也是希望可以分享自己的知識。目前的內容幾乎是基礎知識和技術入門,如果你覺得還可以的話不妨關注一下,我們共同進步!
  • 除了分享博客之外,也喜歡看書,寫一點日常雜文和心情分享,如果你感興趣,也可以關注關注!
  • 微信公衆號:傲驕鹿先生
     

結構型模式主要涉及如何組合各種對象以便獲得更好、更靈活的結構。雖然面向對象的繼承機制提供了最基本的子類擴展父類的功能,但結構型模式不僅僅簡單地使用繼承,而更多地通過組合與運行期的動態組合來實現更靈活的功能。

一、代理模式(Proxy Pattern):靜態代理

1、介紹

  • 定義:給目標對象提供一個代理對象,並由代理對象控制對目標對象的引用

  • 主要作用:通過代理對象的方式間接訪問目標對象

  • 解決的問題:防止直接訪問目標對象給系統帶來不必要的麻煩

2、UML圖及簡單實現

接下來將通過一個簡單的實例來實現:

我想買一臺最新的頂配Mac電腦,但是國內還沒有上只有美國纔有,想找人進行代購。即代購(代理對象)代替我(真實對象)去是買Mac(間接訪問操作)

(1)創建抽象對象接口( Subject ): 聲明我(真實對象)需要讓代購(代理對象)幫忙做的事(買Mac)

public interface Subject {  
    public void buyMac();
}

(2) 步驟2: 創建真實對象類(RealSubject),即”我“

public class RealSubject implement Subject{
  @Override
  public void buyMac() {  
      System.out.println(”買一臺Mac“);  
  }  
}

(3) 步驟3:創建代理對象類(Proxy),即”代購“,並通過代理類創建真實對象實例並訪問其方法

public class Proxy  implements Subject{
  
    @Override
    public void buyMac{
      
      //引用並創建真實對象實例,即”我“
      RealSubject realSubject = new RealSubject();

      //調用真實對象的方法,進行代理購買Mac
      realSubject.buyMac();
      //代理對象額外做的操作
      this.WrapMac();
    }

     public void WrapMac(){
      System.out.println(”用盒子包裝好Mac“);  
    }
}

(4) 步驟4:客戶端調用

public class ProxyPattern {

    public static void main(String[] args){
        Subject proxy = new Proxy();
        proxy.buyMac();
    }
}

3、優缺點

(1)優點

  • 協調調用者和被調用者,降低了系統的耦合度

  • 代理對象作爲客戶端和目標對象之間的中介,起到了保護目標對象的作用

(2)缺點

  • 由於在客戶端和真實主題之間增加了代理對象,因此會造成請求的處理速度變慢;

  • 實現代理模式需要額外的工作(有些代理模式的實現非常複雜),從而增加了系統實現的複雜度。

4、應用場景

一、代理模式(Proxy Pattern):動態代理

1、爲什麼要使用動態代理?

代理模式中的靜態代理模式存在一些特點:

  • 1個靜態代理 只服務1種類型的目標對象

  • 若要服務多類型的目標對象,則需要爲每種目標對象都實現一個靜態代理對象

在目標對象較多的情況下,若採用靜態代理,則會出現 靜態代理對象量多、代碼量大,從而導致代碼複雜的問題,因此可以使用動態代理來解決這個問題。

2、動態代理的實現

接下來還是通過一個實例來實現:

我想買一臺最新的頂配Mac電腦,我朋友想買一臺iPhone XS Max,但是國內還沒有上只有美國纔有,想找人進行代購。即代購(動態代理對象)同時代替我和朋友(目標對象)去買Mac、iPhone XS Max(間接訪問操作)

(1)步驟一:聲明調用處理器類 

<-- 作用 -->
// 1、生成動態代理對象
// 2、指定代理對象運行目標對象方法時需要完成的具體任務
// 注:需實現InvocationHandler接口 = 調用處理器接口
// 所以稱爲調用處理器類
public class DynamicProxy implements InvocationHandler {

    // 聲明代理對象
    // 作用:綁定關係,即關聯到哪個接口(與具體的實現類綁定)的哪些方法將被調用時,執行invoke()
    private Object ProxyObject;

    public Object newProxyInstance(Object ProxyObject){
        this.ProxyObject =ProxyObject;

        // Proxy類 = 動態代理類的主類
        // Proxy.newProxyInstance()作用:根據指定的類裝載器、一組接口 & 調用處理器生成動態代理類實例,並最終返回
        // 參數說明:
        // 參數1:指定產生代理對象的類加載器,需要將其指定爲和目標對象同一個類加載器
        // 參數2:指定目標對象的實現接口
        // 即要給目標對象提供一組什麼接口。若提供了一組接口給它,那麼該代理對象就默認實現了該接口,這樣就能調用這組接口中的方法
        // 參數3:指定InvocationHandler對象。即動態代理對象在調用方法時,會關聯到哪個InvocationHandler對象
        return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(),
                ProxyObject.getClass().getInterfaces(),this);
    }

    //  複寫InvocationHandler接口的invoke()
    //  動態代理對象調用目標對象的任何方法前,都會調用調用處理器類的invoke()
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
        // 參數說明:
        // 參數1:動態代理對象(即哪個動態代理對象調用了method()
        // 參數2:目標對象被調用的方法
        // 參數3:指定被調用方法的參數

        System.out.println("代購出門了");
        Object result = null;
        // 通過Java反射機制調用目標對象方法
        result = method.invoke(ProxyObject, args);
        return result;
    }

}

(2)步驟二:聲明目標對象類的抽象接口

public interface Subject {
    // 定義目標對象的接口方法
    // 代購物品
    public  void buybuybuy();
}

(3)步驟三:聲明目標對象類、

// 我,真正的想買Mac的對象 = 目標對象 = 被代理的對象// 實現抽象目標對象的接口
public class Buyer1 implements Subject  {
    @Override
    public void buybuybuy() {
        System.out.println("我要買Mac");
    }
}

// 朋友,真正的想買iPhone的對象 = 目標對象 = 被代理的對象// 實現抽象目標對象的接口
public class Buyer2 implements Subject  {
    @Override
    public void buybuybuy() {
        System.out.println("朋友要買iPhone");
    }
}

(4)步驟四:通過動態代理對象,調用目標對象的方法

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 1. 創建調用處理器類對象
        DynamicProxy DynamicProxy = new DynamicProxy();

        // 2. 創建目標對象對象
        Buyer1 mBuyer1 = new Buyer1();

        // 3. 創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance()
        // 傳入上述目標對象對象
        Subject Buyer1_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer1);

        // 4. 通過調用動態代理對象方法從而調用目標對象方法
        // 實際上是調用了invoke(),再通過invoke()裏的反射機制調用目標對象的方法
        Buyer1_DynamicProxy.buybuybuy();
        // 以上代購爲我代購Mac

        // 以下是代購爲朋友代購iPhone
        Buyer2 mBuyer2 = new Buyer2();
        Subject Buyer2_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer2);
        Buyer2_DynamicProxy.buybuybuy();
    }
}

3、實現原理、優缺點

(1)實現原理

  • 設計動態代理類(DynamicProxy)時,不需要顯式實現與目標對象類(RealSubject)相同的接口,而是將這種實現推遲到程序運行時由 JVM來實現

  • 即:在使用時再創建動態代理類 & 實例

  • 靜態代理則是在代理類實現時就指定與目標對象類(RealSubject)相同的接口

  • 通過Java 反射機制的method.invoke(),通過調用動態代理類對象方法,從而自動調用目標對象的方法

(2)優點

  • 只需要1個動態代理類就可以解決創建多個靜態代理的問題,避免重複、多餘代碼

  • 更強的靈活性

    • 設計動態代理類(DynamicProxy)時,不需要顯式實現與目標對象類(RealSubject)相同的接口,而是將這種實現推遲到程序運行時由 JVM來實現

    • 在使用時(調用目標對象方法時)纔會動態創建動態代理類 & 實例,不需要事先實例化

(3)缺點

  • 效率低

    相比靜態代理中直接調用目標對象方法,動態代理則需要先通過Java反射機制 從而 間接調用目標對象方法

  • 應用場景侷限

    因爲 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),即只能針對接口創建代理類,不能針對類創建代理類

4、應用場景、與靜態代理的區別

(1)應用場景

  • 基於靜態代理應用場景下,需要代理對象數量較多的情況下使用動態代理

  • AOP 領域

    • 定義:即 Aspect Oriented Programming = 面向切面編程,是OOP的延續、函數式編程的一種衍生範型

    • 作用:通過預編譯方式和運行期動態代理實現程序功能的統一維護。

    • 優點:降低業務邏輯各部分之間的耦合度 、 提高程序的可重用性 & 提高了開發的效率

    • 具體應用場景:日誌記錄、性能統計、安全控制、異常處理

(2)與靜態代理的區別

五、源碼分析

在步驟(4)中有兩個值得重要的源碼分析點:

a、創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance()

b、通過調用動態代理對象方法從而調用目標對象方法

關注一:創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance()

我們直接進入到DynamicProxy.newProxyInstance():

<-- 關注1:調用處理器 類的newProxyInstance() -->// 即步驟1中實現的類:DynamicProxy
// 作用:
// 1、生成動態代理對象
// 2、指定代理對象運行目標對象方法時需要完成的具體任務
// 注:需實現InvocationHandler接口 = 調用處理器 接口

public class DynamicProxy implements InvocationHandler {

    // 聲明代理對象
    private Object ProxyObject;

    public Object newProxyInstance(Object ProxyObject){
        this.ProxyObject =ProxyObject;
        return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(),
                ProxyObject.getClass().getInterfaces(),this);
        // Proxy.newProxyInstance()作用:根據指定的類裝載器、一組接口 & 調用處理器 生成動態代理類實例,並最終返回
        // ->>關注2
    }

    // 以下暫時忽略,下文會詳細介紹
    // 複寫InvocationHandler接口的invoke()
    // 動態代理對象調用目標對象的任何方法前,都會調用調用處理器類的invoke()
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            // 參數1:動態代理對象(即哪個動態代理對象調用了method()
            // 參數2:目標對象被調用的方法
            // 參數3:指定被調用方法的參數
            throws Throwable {
                System.out.println("代購出門了");
                Object result = null;
                // 通過Java反射機制調用目標對象方法
                result = method.invoke(ProxyObject, args);
        return result;
    }

// 至此,關注1分析完畢,跳出}

<-- 關注2:newProxyInstance()源碼解析-->
// 作用:根據指定的類裝載器、一組接口 & 調用處理器 生成動態代理類及其對象實例,並最終返回
      
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException {
    // 參數說明:
    // 參數1:指定產生代理對象的類加載器,需要將其指定爲和目標對象同一個類加載器
    // 參數2:指定目標對象的實現接口
    // 即要給目標對象提供一組什麼接口。若提供了一組接口給它,那麼該代理對象就默認實現了該接口,這樣就能調用這組接口中的方法
    // 參數3:指定InvocationHandler對象。即動態代理對象在調用方法時,會關聯到哪個InvocationHandler對象

    ...  
    // 僅貼出核心代碼
    // 1. 通過爲Proxy類指定類加載器對象 & 一組interface,從而創建動態代理類
    // >>關注3
    Class cl = getProxyClass(loader, interfaces);

    // 2. 通過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型
    Constructor cons = cl.getConstructor(constructorParams);

    // 3. 通過動態代理類的構造函數創建代理類實例(傳入調用處理器對象)
    return (Object) cons.newInstance(new Object[] { h });

   // 特別注意
   // 1. 動態代理類繼承 Proxy 類 & 並實現了在Proxy.newProxyInstance()中提供的一系列接口(接口數組)// 2. Proxy 類中有一個映射表
   // 映射關係爲:(<ClassLoader>,(<Interfaces>,<ProxyClass>) )
   // 即:1級key = 類加載器,根據1級key 得到 2級key = 接口數組
   // 因此:1類加載器對象 + 1接口數組 = 確定了一個代理類實例...


// 回到調用關注2的原處
}

<-- 關注3:getProxyClass()源碼分析 -->
// 作用:創建動態代理類

public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces) throws IllegalArgumentException {
    
  ...
  // 僅貼出關鍵代碼
<-- 1. 將目標類所實現的接口加載到內存中 -->
    // 遍歷目標類所實現的接口  
    for (int i = 0; i < interfaces.length; i++) {  
          
        // 獲取目標類實現的接口名稱
        String interfaceName = interfaces[i].getName();  
        Class interfaceClass = null;  
        try {  
        // 加載目標類實現的接口到內存中  
        interfaceClass = Class.forName(interfaceName, false, loader);  
        } catch (ClassNotFoundException e) {
        }  
        if (interfaceClass != interfaces[i]) {  
        throw new IllegalArgumentException(  
            interfaces[i] + " is not visible from class loader");  
        }  
    }  
       
<-- 2. 生成動態代理類 -->       
        // 根據傳入的接口 & 代理對象 創建動態代理類的字節碼
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);  
        // 根據動態代理類的字節碼 生成 動態代理類
        proxyClass = defineClass0(loader, proxyName,  
        proxyClassFile, 0, proxyClassFile.length);  
       
        }  
    // 最終返回動態代理類
    return proxyClass;  
}  

// 回到調用關注3的原處

總結:

  • 通過調用處理器類對象的.newProxyInstance()創建動態代理類 及其實例對象(需傳入目標類對象)

  • 具體過程如下:

    1. 通過 爲Proy類指定類加載器對象 & 一組接口,從而創建動態代理類的字節碼;再根據類字節碼創建動態代理類

    2. 通過反射機制獲取動態代理類的構造函數(參數類型 = 調用處理器接口類型)

    3. 通過動態代理類的構造函數 創建 代理類實例(傳入調用處理器對象)

關注二:通過調用動態代理對象方法從而調用目標對象方法

在關注1中的 DynamicProxy.newProxyInstance()生成了一個動態代理類及其實例,該動態代理類記爲 :$Proxy0

下面我們直接看該類實現及其 buybuybuy():

<-- 動態代理類 $Proxy0 實現-->
// 繼承:Java 動態代理機制的主類:java.lang.reflect.Proxy
// 實現:與目標對象一樣的接口(即上文例子的Subject接口)
public final class $Proxy0 extends Proxy implements Subject {

    // 構造函數
    public ProxySubject(InvocationHandler invocationhandler){   
        super(invocationhandler);   
    }  

    // buybuybuy()是目標對象實現接口(Subject)中的方法
    // 即$Proxy0類必須實現
    // 所以在使用動態代理類對象時,纔可以調用目標對象的同名方法(即上文的buybuybuy())
    public final void buybuybuy(){
        try {
            super.h.invoke(this, m3, null);
            // 該方法的邏輯實際上是調用了父類Proxy類的h參數的invoke()
            // h參數即在Proxy.newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)傳入的第3個參數InvocationHandler對象
            // 即調用了調用處理器的InvocationHandler.invoke()
            // 而複寫的invoke()利用反射機制:Object result=method.invoke(proxied,args)
            // 從而調用目標對象的的方法 ->>關注4
            return;
        } catch (Error e) {
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

<-- 關注4:調用處理器 類複寫的invoke() -->
// 即步驟1中實現的類:DynamicProxy
// 內容很多都分析過了,直接跳到複寫的invoke()中
public class DynamicProxy implements InvocationHandler {

    private Object ProxyObject;

    public Object newProxyInstance(Object ProxyObject){
        this.ProxyObject =ProxyObject;
        return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(),
                ProxyObject.getClass().getInterfaces(),this);
    }

    //  複寫InvocationHandler接口的invoke()
    //  動態代理對象調用目標對象的任何方法前,都會調用調用處理器類的invoke()
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
        // 參數說明:
        // 參數1:動態代理對象(即哪個動態代理對象調用了method()
        // 參數2:目標對象被調用的方法
        // 參數3:指定被調用方法的參數
        throws Throwable {
            System.out.println("代購出門了");
            Object result = null;
            // 通過Java反射機制調用目標對象方法
            result = method.invoke(ProxyObject, args);
            return result;
    }
}

總結:

  • 動態代理類實現了與目標類一樣的接口,並實現了需要目標類對象需要調用的方法

  • 該方法的實現邏輯 = 調用父類 Proxy類的 h.invoke()

         其中h參數 = 在創建動態代理實例中newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)傳入的第3個參數InvocationHandler對象

  • 在 InvocationHandler.invoke() 中通過反射機制,從而調用目標類對象的方法

6、總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章