設計模式-代理模式

代理模式介紹

代理模式(Proxy Pattern)也稱為委托模式,是結構型設計模式的一種郑诺。在現(xiàn)實生活中用到代理的場景有很多,如:加盟商杉武,去代售點買票,代理上網(wǎng)等辙售。

代理模式定義

為其他對象提供一種代理以控制這個對象的訪問轻抱。

代理模式使用場景

當無法或不想直接引用某個對象或訪問某個對象存在困難時,可以通過代理類對象來間接訪問旦部。

代理模式 UML 類圖


角色介紹:

  • Subject:抽象主題類祈搜,聲明真實主題與代理的共同接口方法。
  • RealSubject:真實主題類士八,該類定義了代理所表示的真實對象容燕,是負責執(zhí)行系統(tǒng)真正的邏輯業(yè)務對象。
  • Proxy:代理類婚度,其內(nèi)部持有 RealSubject 的引用蘸秘,因此具備對 RealSubject 的代理權。

代理模式簡單實現(xiàn)

這里以郵遞物品為例,郵遞公司看做代理(其不負責郵遞),郵遞員為實際郵遞者醋虏。

抽象主題類(Subject)

抽象主題類具有真實主題類和代理的共同接口方法寻咒,需要郵遞物品,則就是郵遞

public interface IPost {
    void post(String goods);
}

真實主題類(RealSubject)

真正負責郵遞物品的是快遞員 Postman

public class Postman implements IPost {

    @Override
    public void post(String goods) {
        System.out.println("郵遞員:郵寄物品為" + goods);
    }
}

代理類(ProxySubject)

起代理作用的是郵遞公司颈嚼,郵遞公司并不進行實際郵遞工作毛秘,其會讓郵遞員進行郵遞,為了避免物品給郵遞員帶來危險阻课,其負責對物品進行安全檢查叫挟。

public class PostProxy implements IPost {

    private Postman mPostman;

    public PostProxy(Postman postman) {
        mPostman = postman;
    }

    @Override
    public void post(String goods) {
        System.out.println("貨物安檢");
        if (goods.contains("handgun")) {
            System.out.println("貨物存在違禁品,無法郵遞");
            return;
        }
        System.out.println("貨物正常限煞,開始郵遞");
        mPostman.post(goods);
    }
}

客戶端

public class Client {
    public static void main(String[] args) {
        Postman postman = new Postman();
        PostProxy postProxy = new PostProxy(postman);
        // 郵遞手機
        postProxy.post("handset");
        // 郵遞手槍
        postProxy.post("handgun");
    }
}

輸出結果如下:

貨物安檢
貨物正常抹恳,開始郵遞
郵遞員:郵寄貨物為handset

貨物安檢
貨物存在違禁品,無法郵遞

代理 PostProxy 完成了物品的安檢和郵遞晰骑。這里先說下我的疑問:已經(jīng)有Postman 對象了适秩,為什么又去調(diào)用 PostProxy?
我們的代理類可以不只為一個真實主題類做代理,我們可以為多個具有郵遞能力的機構做代理硕舆,那么我們就可以將 PostProxy 持有 Postman 對象改為 IPost 接口秽荞。

代理模式的擴展

設計模式中有普通代理強制代理的概念。

  • 普通代理:我們需要知道代理的存在抚官,而不能去訪問真實主題角色扬跋。
  • 強制代理:調(diào)用者直接調(diào)用真實角色,而不用關心代理是否存在凌节,其代理的產(chǎn)生是由真實角色決定钦听。

普通代理示例

基于上述示例稍作修改:

public class Postman implements IPost {
    public Postman(IPost post) throws Exception {
        // 通過是否傳入代理類來驗證創(chuàng)建方
        if (post == null) {
            throw new Exception("不能創(chuàng)建真實主題角色");
        }
    }

    @Override
    public void post(String goods) {
        System.out.println("郵遞員:郵寄貨物為" + goods);
    }
}
public class PostProxy implements IPost {

    private Postman mPostman;

    public PostProxy() {
        try {
            // 有權創(chuàng)建 Postman
            mPostman = new Postman(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void post(String goods) {
        System.out.println("貨物安檢");
        if (goods.contains("handgun")) {
            System.out.println("貨物存在違禁品,無法郵遞");
            return;
        }
        System.out.println("貨物正常倍奢,開始郵遞");
        mPostman.post(goods);
    }
}
public class Client {
    public static void main(String[] args) {
        IPost post = new PostProxy();
        post.post("handset");
    }
}

運行結果完全相同朴上。在該模式下,調(diào)用者只知代理而不用知道真實的角色是誰卒煞,屏蔽了真實角色的變更對高層模塊的影響痪宰。當然,在實際的項目中畔裕,一般都是通過約定來禁止new一個真實的角色衣撬,這也是一個非常好的方案。

強制代理示例

該種代理要求必須通過真實角色查找到代理角色扮饶,否則你無法訪問具练。無論你是通過代理類還是通過直接new一個主題角色類,都無法訪問甜无,只有通過真實角色指定的代理類才可以訪問扛点,也就是說由真實角色管理代理角色哥遮。以明星和他的經(jīng)紀人為例,明星相當于真實角色占键,經(jīng)紀人相當于代理昔善,而你和該明星關系非常好,找他辦事肯定直接給他打電話畔乙,明星最后就找他指定的經(jīng)紀人完成你求的事君仆。

public interface IPost {
    void post(String goods);
    // 都可以有自己指定的代理
    IPost getProxy();
}
public class Postman implements IPost {
    private IPost mProxy;

    @Override
    public void post(String goods) {
        // 是我的代理郵遞,不是則提示
        if (isProxy()) {
            System.out.println("郵遞員:郵寄貨物為" + goods);
        } else {
            System.out.println("請找我的代理進行郵寄");
        }
    }
    
    // 返回我指定的代理
    @Override
    public IPost getProxy() {
        mProxy = new PostProxy(this);
        return mProxy;
    }
    // 判斷是不是我的代理
    private boolean isProxy() {
        return mProxy != null;
    }
}
public class PostProxy implements IPost {
    // 被代理的對象
    private IPost mPoster;

    public PostProxy(IPost post) {
        mPoster = post;
    }
    @Override
    public void post(String goods) {
        mPoster.post(goods);
    }
    // 自己也可以被代理牲距,我為自己代理返咱。
    @Override
    public IPost getProxy() {
        return this;
    }
}

客戶端代碼

public class Client {
    public static void main(String[] args) {
        // 1. 直接通過真實對象郵遞,郵遞失敗
        IPost post = new Postman();
        post.post("handset");
        // 2. 自己尋找郵遞代理牍鞠,郵遞失敗
        IPost proxy = new PostProxy(post);
        proxy.post("handset");
        // 3. 訪問郵遞員指定的代理咖摹,郵遞成功
        post.getProxy().post("handset");
    }
}

輸出結果:

請找我的代理進行郵寄
請找我的代理進行郵寄
郵遞員:郵寄貨物為handset

強制代理的概念就是要從真實角色查找到代理角色,不允許直接訪問真實角色难述。高層模塊只要調(diào)用getProxy就可以訪問真實角色的所有方法萤晴,它根本就不需要自己創(chuàng)建一個代理出來,代理的管理已經(jīng)由真實角色自己完成胁后。

代理類不僅僅可以實現(xiàn)主題接口店读,也可以實現(xiàn)其他接口完成不同的任務,而且代理的目的是在目標對象方法的基礎上作增強攀芯,這種增強的本質(zhì)通常就是對目標對象的方法進行攔截和過濾屯断。

動態(tài)代理模式簡單實現(xiàn)

從編碼的角度來說,代理模式分為靜態(tài)代理動態(tài)代理侣诺,上面的例子是靜態(tài)代理殖演,在代碼運行前就已經(jīng)存在了代理類的class編譯文件,而動態(tài)代理則是在代碼運行時通過反射來動態(tài)的生成代理類的對象年鸳,并確定到底來代理誰颁督。也就是我們在編碼階段不需要知道代理誰圈暗,代理誰我們將會在代碼運行時決定滤淳。Java 給我們提供了一個便捷的動態(tài)代理接口 InvocationHandler赠摇,實現(xiàn)該接口需要重寫invoke()方法并巍。下面我們在上面靜態(tài)代理的例子上做修改:

創(chuàng)建動態(tài)代理類:

public class DynamicProxy implements InvocationHandler {

    // 被代理的類引用
    private Object obj;

    public DynamicProxy(Object obj) {
        this.obj = obj;
    }

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

當調(diào)用代理類的接口方法時忌穿,會調(diào)用到 invoke 方法连躏,該方法中負責反射調(diào)用被代理者的真實邏輯實現(xiàn)的方法石景。

修改客戶端代碼

public class Client {
    public static void main(String[] args) {
        IPost postman = new Postman();
        // 創(chuàng)建動態(tài)代理
        DynamicProxy dynamicProxy = new DynamicProxy(postman);
        // 創(chuàng)建被代理者的 ClassLoader
        ClassLoader loader = postman.getClass().getClassLoader();
        // 創(chuàng)建代理者
        IPost poster = (IPost) Proxy.newProxyInstance(loader, new Class[]{IPost.class}, dynamicProxy);
        poster.post("handgun");
    }
}

輸出結果:

郵遞員:郵寄貨物為handgun

動態(tài)代理通過一個代理類來代理 N 多個被代理者更舞,其實質(zhì)是對代理者和被代理者解耦畦幢,使兩者沒有直接的耦合關系。 而靜態(tài)代理則只對給定的接口下的實現(xiàn)類做代理缆蝉,如果接口不同就需要重新定義代理類宇葱。

代理模式類型

從編碼角度區(qū)分

  • 靜態(tài)代理
  • 動態(tài)代理

從適用范圍區(qū)分

  • 遠程代理(Remote Proxy):為某個對象在不同的內(nèi)存地址空間提供局部代理瘦真。是系統(tǒng)可以將 Server 部分的實現(xiàn)隱藏,以便 Client 可以不必考慮 Server 的存在黍瞧。Android 中 aidl 生成的 Java 代碼 中存在個 Proxy诸尽,該代碼運行在客戶端,而實際邏輯運行在服務端印颤,該Proxy 就是遠程代理您机。
  • 虛擬代理(Virtual Proxy):使用一個代理對象表示一個十分耗資源的對象并在真正需要時才創(chuàng)建。
  • 保護代理(Protection Proxy):使用代理控制對真實對象的訪問年局。該類型的代理常被用于對原始對象有不同的訪問權限的情況际看。上面的靜態(tài)代理示例就屬于該種。
  • 智能引用(Smart Reference):在訪問原始對象時執(zhí)行一些自己的附加操作并對指向原始對象的引用計數(shù)矢否。

靜態(tài)代理和動態(tài)代理都可以應用于上述 4 中情形仲闽。

總結

代理模式優(yōu)點
1.職責清晰:真實的角色就是實現(xiàn)實際的業(yè)務邏輯,不用關心其他非本職責的事務
2.高擴展性:代理類可以完全控制真實主題僵朗,可以控制訪問赖欣,擴展功能,而不用修改原真實主題验庙。
代理模式缺點
1.由于在客戶端和真實主題之間增加了代理對象顶吮,因此有些類型的代理模式可能會造成請求的處理速度變慢。
2.實現(xiàn)代理模式需要額外的工作壶谒,有些代理模式的實現(xiàn)非常復雜云矫。

Android 源碼中代理模式實現(xiàn)

Android 源碼中有很多代理模式的實現(xiàn),比如 AIDL 生成的Java文件中 Proxy類汗菜,這里直接以 Android 7.0 上 ActivityManagerProxy 為例介紹让禀,ActivityManagerProxy 就相當于 AIDL 生成的 Proxy 類,只不過 Framework 中直接以 Java 代碼的形式書寫陨界,在Android 8.0 上已經(jīng)變化為 AIDL 形式巡揍,請知悉。

為了方便菌瘪,下面對ActivityManagerProxy腮敌,ActivityManagerNative 和 ActivityManagerService 會以簡寫方式書寫,分別為: AMP俏扩,AMN 和 AMS糜工。
AMP 具體代理的是 AMN 的子類 AMS, AMP與 AMN 在同一文件中。

class ActivityManagerProxy implements IActivityManager {
  ...
}

IActivityManager 為接口類就是代理模式中的抽象主題录淡,其中定義了一些 Activity 相關的接口方法

public interface IActivityManager extends IInterface {
  ...
  public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
            ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
  public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter,
            String requiredPermission, int userId) throws RemoteException;
  public int checkPermission(String permission, int pid, int uid)
            throws RemoteException;
  ...
}

對于真正的主題角色是誰呢捌木?就是繼承 AMN 的 AMS,這幾個類大致關系如下圖:


通過 UML 圖可以清晰的看出 AMP 和 AMN 都實現(xiàn)了 IActivityManager 接口嫉戚,嚴格地說刨裆,AMP 就是代理部分澈圈,而 AMN 就是真實主題角色,但 AMN 是抽象類帆啃,并不處理過多邏輯瞬女,大部分是由 AMS 完成。
AMS 是系統(tǒng)級Service努潘,運行在system_server進程中诽偷,而 AMP 一般運行在客戶端進程也就是 app 進程,他們之間的通信屬于 Binder 跨進程通信慈俯。對于 AMP 并不是在 app 進程直接使用的渤刃,而是通過 ActivityManager 類,該類負責管理和維護 Activity 相關信息贴膘,但實際大多數(shù)邏輯是通過 AMP 承擔卖子。這里以 getMemoryInfo 為例介紹是如何工作的。
ActivityManager.java

public void getMemoryInfo(MemoryInfo outInfo) {
    try {
        ActivityManagerNative.getDefault().getMemoryInfo(outInfo);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

ActivityManagerNative.java
ActivityManagerNative.getDefault() 該方法實現(xiàn)如下

static public IActivityManager getDefault() {
    return gDefault.get();
}

gDefault 又是什么刑峡?

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        // 1. 與ServiceManger 進行 IPC 通信洋闽,獲取系統(tǒng)級 Service,該Service 實質(zhì)上是 AMS
        IBinder b = ServiceManager.getService("activity");
        // 2. 返回 AMP
        IActivityManager am = asInterface(b);
        return am;
    }
};
// 跨進程通信突梦,則返回的是 AMP
static public IActivityManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IActivityManager in =
        (IActivityManager)obj.queryLocalInterface(descriptor);
    if (in != null) {
        return in;
    }
    return new ActivityManagerProxy(obj);
}

gDefault.get() 就是單例的 AMP 對象
ActivityManagerProxy.java

public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    // 1.
    mRemote.transact(GET_MEMORY_INFO_TRANSACTION, data, reply, 0);
    reply.readException();
    outInfo.readFromParcel(reply);
    data.recycle();
    reply.recycle();
}

該段就是 Binder 通信的核心代碼诫舅,在注釋1處,調(diào)用客戶端Binder的 transact() 方法宫患,服務端 Binder 的 onTransact() 就會回調(diào)刊懈,這個是由 Binder 驅(qū)動進行完成⊥尴校客戶端 transact 方法中傳入方法標識為GET_MEMORY_INFO_TRANSACTION虚汛,data 用于向服務端寫入數(shù)據(jù),reply 用于接收服務端的返回數(shù)據(jù)皇帮,當服務端對應方法執(zhí)行完后, transact 就停止阻塞卷哩,繼續(xù)走到outInfo.readFromParcel(reply);,將返回結果寫入outInfo 對象中。接下來我們看服務端是 AMS 是如何執(zhí)行的属拾?
onTransact 方法在 抽象類 AMN 中

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
    switch (code) {
    ...
    case GET_MEMORY_INFO_TRANSACTION: {
        data.enforceInterface(IActivityManager.descriptor);
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        getMemoryInfo(mi);
        reply.writeNoException();
        mi.writeToParcel(reply, 0);
        return true;
    }
    ...
}

實際是通過 getMemoryInfo 方法完成
ActivityManagerService.java

@Override
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
    final long homeAppMem = mProcessList.getMemLevel(ProcessList.HOME_APP_ADJ);
    final long cachedAppMem = mProcessList.getMemLevel(ProcessList.CACHED_APP_MIN_ADJ);
    outInfo.availMem = Process.getFreeMemory();
    outInfo.totalMem = Process.getTotalMemory();
    outInfo.threshold = homeAppMem;
    outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((cachedAppMem-homeAppMem)/2));
    outInfo.hiddenAppThreshold = cachedAppMem;
    outInfo.secondaryServerThreshold = mProcessList.getMemLevel(
            ProcessList.SERVICE_ADJ);
    outInfo.visibleAppThreshold = mProcessList.getMemLevel(
            ProcessList.VISIBLE_APP_ADJ);
    outInfo.foregroundAppThreshold = mProcessList.getMemLevel(
            ProcessList.FOREGROUND_APP_ADJ);
}

AMS 中完成獲取 Memory 信息的獲取将谊,最后返回到客戶端中調(diào)用時傳入的 MemoryInfo 對象中。

客戶端和具體主題角色 AMS 屬于不同進程渐白,所以這里的 AMP 就是遠程代理尊浓,使系統(tǒng)可以將服務端的實現(xiàn)隱藏,客戶端使用時無需關心 AMS纯衍。對于訪問 AMS 都交由 AMP眠砾,由于牽扯到跨進程,調(diào)用 AMS 代碼會比較復雜,這些都有 AMP 完成了處理褒颈,簡化了客戶端的訪問。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末励堡,一起剝皮案震驚了整個濱河市谷丸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌应结,老刑警劉巖刨疼,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鹅龄,居然都是意外死亡揩慕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門扮休,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迎卤,“玉大人,你說我怎么就攤上這事玷坠∥仙Γ” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵八堡,是天一觀的道長樟凄。 經(jīng)常有香客問我,道長兄渺,這世上最難降的妖魔是什么缝龄? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮挂谍,結果婚禮上叔壤,老公的妹妹穿的比我還像新娘。我一直安慰自己凳兵,他們只是感情好百新,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著庐扫,像睡著了一般饭望。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上形庭,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天铅辞,我揣著相機與錄音,去河邊找鬼萨醒。 笑死斟珊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的富纸。 我是一名探鬼主播囤踩,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼旨椒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了堵漱?” 一聲冷哼從身側(cè)響起综慎,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎勤庐,沒想到半個月后示惊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡愉镰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年米罚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丈探。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡录择,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出类嗤,到底是詐尸還是另有隱情糊肠,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布遗锣,位于F島的核電站货裹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏精偿。R本人自食惡果不足惜弧圆,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望笔咽。 院中可真熱鬧搔预,春花似錦、人聲如沸叶组。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甩十。三九已至船庇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侣监,已是汗流浹背鸭轮。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留橄霉,地道東北人窃爷。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親按厘。 傳聞我的和親對象是個殘疾皇子医吊,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361

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