使用組合的設計模式 —— 追女孩要用的遠程代理模式

這是設計模式系列的第三篇,系列文章目錄以下:java

  1. 一句話總結異曲同工的設計模式:工廠模式=?策略模式=?模版方法模式android

  2. 使用組合的設計模式 —— 美顏相機中的裝飾者模式編程

  3. 使用組合的設計模式 —— 追女孩要用的遠程代理模式設計模式

  4. 用設計模式去掉不必的狀態變量 —— 狀態模式緩存

上一篇講了一個使用組合的設計模式:裝飾者模式。它經過繼承複用了類型,經過組合複用了行爲,最終達到擴展類功能的目的。bash

這一篇的代理模式也運用了組合的實現方法,它和裝飾者模式很是像,比較它們之間微妙的差異很是有意思。網絡

幹嗎要代理?

代理就是幫你作事情的對象,爲啥要委託它幫你作?由於作這件事太複雜,有一些你不須要了解的細節,因此將它委託給一個專門的對象來處理。ide

就比如現實生活中的簽證代理,各國辦簽證的須要的材料和流程不盡相同,有一些及其複雜。因此委託給瞭解這些細節的簽證代理幫咱們處理(畢竟還有一大推bug等着咱們)。post

在實際編程中,複雜的事情可能有這麼幾種:遠程對象訪問(遠程代理)、建立昂貴對象(虛擬代理)、緩存昂貴對象(緩存代理)、限制對象的訪問(保護代理)等等。ui

本地 & 遠程

java 中,遠程和本地劃分的標準是:「它們是否運行在同一個內存堆中」

  • 一臺計算機上的應用經過網絡調用另外一臺計算機應用的方法叫作遠程調用,由於兩個應用程序運行在不一樣計算機的內存堆中。
  • Android 系統中,每一個應用運行在各自的進程中,每一個進程有獨立的虛擬機,因此它們運行在同一臺計算機內存的不一樣堆中,跨進程的調用也稱爲遠程調用。

遠程調用 & 遠程代理

遠程調用比本地調用複雜,由於須要處理本地和遠程的通訊(網絡或跨進程調用)。

調用的發起者其實不必瞭解這些細節,它最好只是簡單地發起調用而後拿到想要的結果。因此將這些複雜的事情交給代理來作。(固然也能夠將發起遠程調用的細節和調用發起的業務邏輯寫在一塊兒,面向過程的代碼就是這樣作的)

就以 Android 中的跨進程通訊爲例:發起調用的應用稱爲客戶端,響應調用的應用稱爲服務端。服務以接口的形式定義在一個後綴爲aidl的文件中:

//如下是IMessage.aidl文件的內容
package test.taylor.com.taylorcode;
interface IMessage {
    //系統本身生成的接口
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
    //這是咱們定義的服務接口
    int getMessageType(int index) ;
}
複製代碼

系統會自動爲IMessage.aidl文件生成對應的IMessage.java文件:

public interface IMessage extends android.os.IInterface {
    //樁
    public static abstract class Stub extends android.os.Binder implements test.taylor.com.taylorcode.IMessage {
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        //客戶端調用這個接口獲取服務
        public static test.taylor.com.taylorcode.IMessage asInterface(android.os.IBinder obj) {
            //建立代理對象(注入遠程對象obj)
            return new test.taylor.com.taylorcode.IMessage.Stub.Proxy(obj);
        }
        
        //代理
        private static class Proxy implements test.taylor.com.taylorcode.IMessage {
            //經過組合持有遠程對象
            private android.os.IBinder mRemote;
            //注入遠程對象
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
            
            //代理對象對服務接口的實現
            @Override
            public int getMessageType(int index) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    //包裝調用參數
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(index);
                    //發起遠程調用(經過一些natvie層方法最終會調用服務端實現的stub中的方法)
                    mRemote.transact(Stub.TRANSACTION_getMessageType, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
    }
}
複製代碼

(爲了聚焦在代理這個概念上,代碼省略了大量無關細節。)

系統自動生成了兩個跨進程通訊關鍵類:Stub樁Proxy代理。它們是 Android 跨進程通訊中成對出現的概念。是服務端對服務接口的實現,代理是客戶端對於樁的代理。 暈了。。爲啥要整出這麼多概念,搞這麼複雜?

實際上是爲了簡化跨進程通訊的代碼,將跨進程通訊的細節封裝在代理中,客戶端能夠直接調用代理類的方法(代理和客戶端處於同一內存堆,因此也稱爲遠程的本地代理),由代理髮起跨進程調用並將結果返回給客戶端。代理扮演着屏蔽複雜跨進程通訊細節的做用,讓客戶端覺得本身直接調用了遠程方法。

樁和代理擁有相同的類型,它們都實現了服務接口IMessage,但樁是抽象的,具體的實現會放在服務端。服務端一般會在 Android 系統組件 Service 中實現樁:

public class RemoteServer extends Service {
    public static final int MESSAGE_TYPE_TEXT = 1;
    public static final int MESSAGE_TYPE_SOUND = 2;
    
    //實現樁
    private IMessage.Stub binder = new IMessage.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {}

        //定義服務內容
        @Override
        public int getMessageType(int index) throws RemoteException {
            return index % 2 == 0 ? MESSAGE_TYPE_SOUND : MESSAGE_TYPE_TEXT;
        }
    };

    //將服務實例返回給客戶端
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}
複製代碼

客戶端經過綁定服務來獲取服務實例:

IMessage iMessage;
Intent intent = new Intent(this, RemoteServer.class);
ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        //將服務實例(樁)傳遞給asInterface(),該方法會建立本地代理並將樁注入
        iMessage = IMessage.Stub.asInterface(iBinder);
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        iMessage = null;
    }
};
//綁定服務
this.bindService(intent, serviceConnection, BIND_AUTO_CREATE);
複製代碼

當綁定服務成功後,onServiceConnected()會被回調,本地代理會被建立,而後客戶端就能夠經過iMessage.getMessageType()請求遠程服務了。

遠程代理模式 vs 裝飾者模式

遠程代理運用了和裝飾者模式一摸同樣的實現方式,將它們倆的描述放在一塊兒會顯得頗有趣:

  • 裝飾者和被裝飾者具備相同的類型,裝飾者經過組合持有被裝飾者
  • 代理和被代理者具備相同的類型,代理經過組合持有被代理者

頭大。。。既然同樣爲啥還要區分紅兩種模式?但若是結合它們的意圖進行比較就能發現細微的差異:

  • 裝飾者模式經過 繼承 + 組合 的方式,在複用原有類型和行爲的基礎上爲其擴展功能
  • 遠程代理模式經過 繼承 + 組合 的方式,實現對代理對象的訪問控制

若是硬要用裝飾者模式的臺詞來形容代理模式也沒有什麼不能夠:「代理經過裝飾被代理者,爲其擴展功能,使得它可以被遠程對象訪問」。這句話徹底說得通,可是有點怪怪的。

若是試着添加一點擬人色彩,遠程代理模式和裝飾者模式就變得很好區分!

  • 使用代理模式就好像在說:「我喜歡你,可是我夠不到你。因此我須要代理(多是你的閨蜜)」。
  • 使用裝飾者模式就好像在說:「我喜歡和你相同類型的另外一我的。因此我須要把你裝飾成它。」(好了,你找不到女友了)

後續

本打算用 1篇文章來總結那些使用組合的設計模式,其中包括裝飾者模式、代理模式、適配器模式、外觀模式、狀態模式。

千千沒想到寫着寫着就變成了n 篇。。。。

萬萬沒有想到,這一篇代理模式寫着寫着就發現,若是把全部應用場景講完,篇幅就太長了,無奈之下只能在此留白。代理模式的變種特別多,它們之間在實現方式上和意圖上有微妙的差異,待下回分析。

相關文章
相關標籤/搜索