代理模式

蔡徐坤1987發表於2024-04-14

介紹

代理模式也稱為委託模式,是一種結構性設計模式。

說到代理,可能大部分人都會有一種陌生又熟悉的感覺,日常生活中好像都能遇到,比如代理上網,招商代理,商務代理等;但又說不出個具體的一二三來;代理這個事情如果我們換個角度,從委託者的角色出發,我們找代理上網,是因為我們在訪問某些網站時存在困難,需要有個角色來間接的幫我們實現這個功能;我們找商務代理,可能是因為許多事我們不在行或者其他原因,需要找專業的中間人來幫我們做事。因此,日常生活中我們更多扮演的是委託人的角色,代理以一種中間人的角色,幫我們是處理我們無能為力的事情。

如果從寫程式碼的角度出發,當我們遇到以下場景:

  • 無法直接訪問某個物件
  • 不想直接訪問某個物件
  • 訪問某個物件存在困難

的時候,我們就可以通過一個代理,通過它來間接訪問真正的物件。

定義及UML圖

定義:

為目標物件提供一種代理,客戶端通過代理去訪問目標物件。

UML 圖

代理模式

從代理模式的UML 類圖中,我們可以得到如下結論:

  • 代理物件和委託物件需要實現相同的介面(抽象類)
  • 代理物件持有委託物件的引用

可以看到,代理模式非常簡潔,總共就三個角色,包括抽象主題,委託者和代理者,下面用程式碼簡單實現一下基礎的代理模式。

public interface Subject {
    void doSomething();
}

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("This is real doSomeThing");
    }
}

public class ProxySubject implements Subject {

    private Subject mSubject;
    // 代理類持有委託類的引用
    public ProxySubject(Subject realSubject) {
        mSubject = realSubject;
    }

    @Override
    public void doSomething() {
        mSubject.doSomething();
    }
}

public class Client {
    public static void main(String[] args) {
        //建立委託類
        Subject mRealSubject=new RealSubject();
        //建立代理類
        ProxySubject mProxy = new ProxySubject(mRealSubject);
        //由代理類去做具體的操作
        mProxy.doSomething();
    }
}

複製程式碼

可以看到RealSubject和ProxySubject都實現了介面Subject。在客戶端使用ProxySubject的例項呼叫doSomething方法,而不是使用RealSubject的例項來實現。

你可能會好奇,這麼做的意義是什麼呢?直接用RealSubject的例項來呼叫doSomething方法不也可以嗎?何必多此一舉。試想,如果現在有很多個委託類,他們各自的實現都不同,客戶端只關心doSomething 的呼叫,而不關心具體的實現,這樣代理類就可以在其內部遮蔽委託類之間的差異了,這也是客戶端不想關注的事情。這麼說可能有點暈,下面就通過Android原始碼中的實現來感受一下。

Android 中的代理模式

平時寫程式碼的時候,可能感覺代理模式沒怎麼遇到過。其實不然,甚至可以說代理模式是我們最常用到的一種設計模式。這裡就來看看幾乎天天都在使用的AppCompatActivity。

最早的時候,我們建立自己的Activity都是直接繼承android.app.Activity。後來隨著Android版本的升級,我們建立的Activity會繼承AppCompatActivity。這裡的Compat其實就是Compatible(相容)的縮寫,那麼他是怎麼實現相容的呢。

onCreate

onCreate()方法是整個Activity生命週期的開始。AppCompatActivity又是怎麼實現他的呢。

AppCompatActivity-onCreate()

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
		……
    }
複製程式碼

可以看到這裡他並沒具體去實現onCreate,而是使用一個AppCompatDelegate例項的onCreate()方法去實現。繼續看getDelegate 的實現。

AppCompatActivity-getDelegate()

    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
複製程式碼

可以看到這個例項建立是在AppCompatDelegate類中。接著看create的實現

AppCompatDelegate-create()

    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return create(activity, activity.getWindow(), callback);
    }

    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        if (Build.VERSION.SDK_INT >= 24) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }
複製程式碼

可以看到,這裡就不同的Android版本,分別返回了不同的AppCompatDelegate。如果去看原始碼(這裡的原始碼分析不是重點,就不貼出了,直接給出結論),我們會發現,從 AppCompatDelegateImplN到AppCompatDelegateImplV9,是子類到父類的關係,之間是依次繼承。而AppCompatDelegateImplV9又繼承自AppCompatDelegateImplBase(抽象類),而這個AppCompatDelegateImplBase則是繼承自AppCompatDelegate。

到這裡,結合一開始我們所說的代理模式的內容,我們很容易總結出以下結論:

  • AppCompatDelegate 同時兼顧了抽象主題和代理類的角色
  • AppCompatDelegateImplN,AppCompatDelegateImplV23等這些都是委託類,他們都繼承自 AppCompatDelegate 類。

通過AppCompatDelegate.java(點選可直接檢視)的原始碼,我們可以發現,這個抽象類內部定義了一系列和 Activity 相關的抽象方法,包括Activity生命週期函式,setContentView,setSupportActionBar等。我們知道,子類通過繼承父類,可以擴充套件(spuer)或直接覆蓋父類的方法實現。 AppCompatDelegateImplV9 這個類是AppCompatDelegate的具體實現,之後的版本,就可以通過繼承AppCompatDelegateImplV9來擴充套件或修改一些方法實現,通過AppCompatDelegate 在create方法中建立不同的委託類來完成不同的實現,而我們原先寫好的程式碼也不會被破壞,可以看到Android原始碼對Activity相容這個事做的非常巧妙。AppCompatDelegate主要是對ActionBar的相容及夜間模式的處理做了一些方便開發者實現的處理;這裡就不再具體分析了。

當然,代理模式這個幾乎找不到缺點的設計模式,在Android原始碼中的應用也是比較廣泛,基本上關於相容性的實現,都會用到以上思路,比如NotificationCompatImpl幾乎使用了和AppCompatDelegate同樣的思路,實現了在手機通知欄中實現不同的通知樣式。除了相容性的處理,另外一個比較經典的實現就是Binder了,作為跨程式通訊的核心理念,Binder巧妙的使用了代理模式,處理了我們無法在不同應用之間共享和傳遞資料的問題。關於Binder的分析,網上真的太多了,這裡就不再贅述了,有興趣的同學可以看看這篇代理模式在Binder中的使用.

動態代理

以上的分析中,代理類是由我們直接建立好的;現實中可能還有這樣一種場景,代理類並不是在程式編譯的時候建立,而是在執行的過程中通過Java的反射機制動態的進行建立,這樣的代理模式成為動態代理,對應的之前我們所說的就是靜態代理了。

其實,動態代理的實現沒有什麼可說的,說白了都是模板程式碼,Java為開發者提供了InvocationHandler,實現該介面重寫其invoke 方法即可。

還是以之前的Subject為例

public interface Subject {
    void doSomething();
}

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("This is real doSomeThing");
    }
}


public class DynamicProxyHandler implements InvocationHandler {
    private Object mObject;


    public DynamicProxyHandler(Object object) {
        mObject = object;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        return method.invoke(mObject, objects);
    }
}


public class MainClass {
    public static void main(String[] args) {
        // 委託類
        Subject mRealSubject = new RealSubject();
        // 委託類classLoader
        ClassLoader mClassLoader = mRealSubject.getClass().getClassLoader();
        // 委託類對應的ProxyHandler
        DynamicProxyHandler mProxyHandler = new DynamicProxyHandler(mRealSubject);
        Class[] mClasses = new Class[]{Subject.class};
        // 代理類
        Subject proxySubject = (Subject) Proxy.newProxyInstance(mClassLoader, mClasses, mProxyHandler);
        // 代理類呼叫方法
        proxySubject.doSomething();
        
    }
}

複製程式碼

這裡可以看到,DynamicProxyHandler 內部持有的並不是一個具體的物件,而是 Object 類,而在其 invoke 方法中,又會根據具體的 Object 物件及引數呼叫其對應的方法。這樣當我們在客戶端呼叫時,完全是根據委託類通過 Proxy.newProxyInstance 方法動態的建立代理類。在上面的程式碼中,我們是通過委託類 RealSubject 動態的建立了一個代理類,通過代理類呼叫抽象主題中定義好的方法,實際上就會呼叫委託類中的具體實現。而在Java中,我們可以通過反射機制,動態的建立類及其例項,因此,我們便可以在執行時通過不同的委託類,更靈活的建立代理類,從而實現不同的功能。

關於動態代理,這篇十分鐘理解Java之動態代理分析的非常好,有興趣的同學可以再看看。

在Android中,關於動態代理的使用,最經典的莫過於這幾年最火熱的Retrofit了。這裡可以簡單看一下。


public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos("octocat");
複製程式碼

上面的實現,現在大家應該很熟悉了,當我們用Retrofit例項,呼叫其create方法時,到底發生了什麼呢?

  public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }
複製程式碼

可以看到,這裡就是一個典型的動態代理實現,通過serviceMethod.callAdapter.adapt返回了一個service物件的代理物件,在上面的例子裡,就是返回了一個GitHubService的代理物件,這樣我們就可以通過這樣一個物件去呼叫GitHubService中定義好的各種網路請求,而不用在使用的時候再去決定是POST請求還是GET請求,引數是放在Body裡還是params裡,因為Retrofit 通過把反射註解和動態代理的巧妙結合,遮蔽了複雜的引數拼接操作,把所有我們需要對OKHttp的進行傳遞的引數,動態的幫我們傳遞了,一旦在介面中定義好了使用方式,就可以非常方便的獲取到okhttp中最關鍵的Call了,有了Call我們就可以通過execute或者是enqueue發起網路請求了。

總結

以上就是對代理模式的分析,總的來說代理模式的結構非常簡單;包括抽象主題,委託類,代理類三個核心角色,從大的方向上可以分為靜態代理和動態代理兩大類;通過靜態代理的方式,在開發迭代的過程中,為實現相容性提供了一種非常友好的實現思路;在日常開發中,如果我們使用的物件之間有著強烈的耦合,可是思考一下是否可以通過代理模式解耦;同時,當我們需要擴充套件某個類的部分功能時,但又不想去破壞原有的功能或者是根本無法修改時,我們可以考慮代理模式,但也要明白,通過代理模式我們能做的也只能是功能擴充套件,想要更新委託類中已經實現的內容他是無能為力的。

動態代理,可以根據執行時的委託類動態的生成代理類,這樣就減輕了代理類的負擔,避免在編碼階段就具體的委託類再做各種判斷了。

代理模式很簡單,也很實用,但不要忘記代理類委託類需要實現功能的介面或抽象類,不要忽略了這一點。

好了,關於代理模式的分析就到這裡了


相關文章