設計模式解析之代理模式

設計模式-代理模式


代理模式的概念

  代理模式(proxy pattern)是一種結構型的設計模式,代理模式在程序開發中的運用非常廣泛。簡單地描述代理模式就是:爲其他對象(被代理對象)提供一種代理以控制對原有對象的操作。實際的行爲是由被代理對象完成的
  代理模式可以分爲兩部分,靜態代理動態代理,它們的區別將在下面詳細介紹。

角色介紹

  Suject: 抽象主題類
  該類的主要職責是申明真是主題與代理的共同接口方法,該類既可以是個抽象類也可以是個接口(具有抽象方法)。
  RealSubject: 真實主題類
  該類也稱爲委託類或者被代理類,改類定義了代理所表示的真是對象(也就是實現了抽象方法),由其執行具體的業務邏輯。
  ProxySubject:代理類
  這個類的對象持有一個對真實主題的引用,在這個類所實現的接口方法中調用真實主題類中相應的方法執行,這樣就實現了代理的目的。
  Client:客戶類
  也就是使用代理類的類型,客戶類通過代理類間接地調用了真實主題類中定義的方法。

代理模式的實現

簡單的例子

針對,上方的角色介紹,舉一個簡單的例子:在現實的世界中,打公司一般有,原告 和原告的代理律師這樣兩個角色,他們要做的事情是 辯護。於是,我們可以實現以下幾個類。
抽象主題類:interface IDefender,這個接口中有個 抽象方法 abstract public void defend(); 表示辯護這個行爲。

public interface IDefender {
    abstract public void defend();//辯護行爲
}

真實主題類: class Accuser,實現IDefender這個接口,具有具體的邏輯行爲

  public class Accuser implements IDefender {

    @Override
    public void defend() {
        System.out.println("被告嚴重侵犯了公民的人身自由權...嗶哩嗶哩");
    }
}

代理類:AccuserProxy 類,原告請了個代理律師幫助它打官司,也就是AccuserProxy,由代理律師控制原告的表現

public class AccuserProxy implements IDefender {
    //持有被代理類的引用
    private IDefender iDefend;

    public AccuserProxy(IDefender iDefend) {
        this.iDefend = iDefend;
    }

    @Override
    public void defend() {
        beforeDefend();
        // 被代理類的行爲
        iDefend.defend();
        afterDefend();
    }
    //修飾的方法
    private void beforeDefend(){
        System.out.print("我是原告律師,以下是我方辯詞");
    }
    //修飾的方法
    private   void afterDefend(){
        System.out.print("辯護完畢");
    }
}

客戶端:Client類,使用代理類的角色

public class Client {
    public static void main(String[] args) {
        Accuser accuser = new Accuser();
        accuser.defend();//此時輸出 被告嚴重侵犯了公民的人身自由權...嗶哩嗶哩

        AccuserProxy accuserProxy = new AccuserProxy(accuser);
        accuserProxy.defend();
        // 輸出:我是原告律師,以下是我方辯詞,被告嚴重侵犯了公民的人身自由權...嗶哩嗶哩,辯護完畢

        /////////////////////////////////////////////////////
        // 因爲,我們的代理類和被代理類 實現的是同一接口,如果將引用類型 寫成IDfender,
        // 那麼在調用 defend(),方法的使用 客戶完全感覺不到被代理類的存在,當然因爲我們這裏的
        // 被代理類是通過構造函數傳進去的,軟件開發中,有時候直接在被代理類中實例化代理類,這樣使用起來就更完美了。
        IDefender accuser2 = new Accuser();
        IDefender accuser2Proxy = new AccuserProxy(accuser2);

        accuser2.defend();
    }
}

  代理模式的運用符合開閉原則的定義,軟件中的對象(類、模塊、函數)應該對於拓展是開發的,對於修改是封閉的。我們不需要修改被代理類 (Accuser),使用代理模式就可以實現對原有功能的加強。以上代碼只是一個簡答的運用場景,現實開發中代理模式的運用非常廣泛,它可以解決多方面的問題。

Androd 開發中的運用例子

  Android API的版本迭代很快,在每次的版本更新中 通常會加強一些原有的功能,對原有的類會增加新的接口,而每個版本能夠調用的API 可能會都不同,比如 狀態欄 Notification

Notfification可以分爲4類,一類是正常視圖,也就是我們通常在狀態欄看到的 高度爲 64dp 的長條狀通知視圖;一類是在 API16 中引入的以 Style 方式展示的 MediaStyleInboxStyleBigTextStyleBigPictureStyle 四種Notification 風格樣式;一類也是在 API16 中引入的可以將通知視圖顯示爲256dp 高度大視圖的 bingContentView;最後一類是在 L 中引入的 headsUpContentVIew

  現在,我們的 App需要根據不同的版本實例化不同 Notification 顯示不同的視圖,在高版本顯示的視圖當然就更豐富,低版本更單調。如果你直接在 Activity 中判斷版本,加上一坨的 switch 或者 if else 語句當然也是可以的。但是,我們現在來看一下 運用設計模式的思想,如何將代碼抽離,對於客戶端來說(Activity or Fragment),不應該關心這些細節。
以下的代碼來自《Android源碼設計模式解析與實戰》一書。

抽象主題類: Notify類, Notify抽象類 聲明瞭 NotificationManager 和NotificatoinCompat.Builder 2個成員變量來處理和通知的一些邏輯。在構造函數中初始化所有子類都會調用的邏輯方法。

public abstract class Notify {
    protected Context context;
    protected NotificationManager nm;
    protected NotificationCompat.Builder builder;

    public Notify(Context context){
        this.context = context;
        nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
        builder = new NotificationCompat.Builder(context);
        builder.setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(PendingIntent.getActivities(
                        context,
                        0,
                        new Intent[]{new Intent(context, NotifyActivity.class)},
                        PendingIntent.FLAG_UPDATE_CURRENT));
    }
    /**
     * 發送一條通知
     */
    public abstract void send();
    /**
     * 取消一條通知
     */
    public abstract void cancel();
}

真實主題類:NotifyNormal ,繼承抽象主題類,簡單重寫抽象方法

public class NotifyNormal extends Notify{
    public NotifyNormal(Context context) {
        super(context);
    }

    @Override
    public void send() {
        Notification n = builder.build();
        n.contentView = new RemoteViews(context.getPackageName(),
                R.layout.remote_notify_proxy_normal);
        nm.notify(0,n);
    }

    @Override
    public void cancel() {
        nm.cancel(0);
    }
}

真實主題類 : 與NormalNotify的不同在於 還實現了bigContentView 的初始化,這是在 API 16以上才能調用的

public class NotifyBig extends Notify{

    public NotifyBig(Context context) {
        super(context);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void send() {
        Notification n = builder.build();
        n.contentView = new RemoteViews(context.getPackageName(), R.layout.remote_notify_proxy_normal);
        n.bigContentView = new RemoteViews(context.getPackageName(),R.layout.remote_notify_proxy_big);
        nm.notify(0,n);
    }

    @Override
    public void cancel() {
        nm.cancel(0);
    }
}

  源碼中還有個 NotifyHeadUp 類,它的唯一區別就是 在bigContentVie 的基礎上還能實現 Notification 的 headsUpContentView ,這個 View 是在 API 20以上 才能使用的,當我們的 App 以全屏的方式展開的時候如果收到了通知,這個 View 就會浮動展示於屏幕頂部。

  代理類: NotifyProxy ,持有被代理類對象,在這個示例中,我們根據不同的運行時環境 API 實例化不同的 被代理類對象。

public class NotifyProxy extends Notify{
    //代理類持有被代理類的引用
    private Notify notify;

    public NotifyProxy(Context context) {
        super(context);
        //根據版本實例化不同的被代理對象
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            notify = new NotifyHeadUp(context);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
            notify = new NotifyBig(context);
        } else {
            notify = new NotifyNormal(context);
        }
    }

    @Override
    public void send() {
        notify.send();
    }

    @Override
    public void cancel() {
        notify.cancel();
    }
}

  客戶端類:也就是我們的Activity,在 Activity 中,我們直接將 Context 傳入,代理類會幫我們實例化合適的被代理對象。

new NotifyProxy(NotifyActivity.this).send();

  在這個實例中,我們通過代理模式 ,使用一個代理類來針對不同的運行時系統版本,實例化不同的 Notificaition 的子類,而在客戶端中 簡單地調用代理類的send方法就可以。

靜態代理模式總結

  代理模式通過代理類對外部提供統一的接口,在代理類中實現對被代理類的附加操作,從而可以在不影響外部調用的情況下實現系統的拓展,我覺得代理模式可能在一個程序項目的開發初期運用不到,而在項目成型而又有了新的變化、升級等,可以考慮用代理模式來實現,這樣可以不需要修改原有的代碼。

動態代理模式

  其實上文所講述的內容只是代理模式的一部分,代理模式還有更爲強大的動態代理模式。以下是這 2 個的區別:

靜態代理模式:在我們的代碼運行前,代理類的class編譯文件就已經存在了
動態代理模式:在 code 階段並不存在被代理類,而且並不知道要代理哪個對象,利用 Java 的反射機制在運行期動態地生成代理者的對象,代理誰將會在代碼執行階段決定。

動態代理模式的實現

  Java 已經爲我們提供了一個便捷的動態代理接口 InvocationHandler ,我們重寫其調用方法 invoke。以前面 我們的代理律師的例子爲例,看看具體的操作是怎麼樣的

public class DynamicProxy implements InvocationHandler{
    private Object object;// 被代理類的類引用


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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeDefend();
        //調用被代理類對象的方法
        Object result = method.invoke(object, args);
        afterDefend();
        return result;
    }

    private void beforeDefend(){
        System.out.print("我是原告律師,以下是我方辯詞");
    }
    private   void afterDefend(){
        System.out.print("辯護完畢");
    }
}

  解釋一下這個 invoke 方法,我們通過 invoke 方法來調用具體的被代理方法,也就是真實的方法,如果對反射機制瞭解的話, method.invoke(object,args) 這句代碼應該很好理解,object 是被代理類,method 是被代理類的方法,args 是方法的參數。

  我們僅僅是實現了 InvocationHandler 的接口,那麼接下來該做什麼呢?怎麼實現動態生成代理者對象呢?Java 的 java.lang.reflect 包下 還有一個 Proxy 類,它有個靜態方法 newProxyInstance(),我們使用這個方法生成。以前面 我們的代理律師的例子爲例,

public class Client {
    public static void main(String[] args) {
        //被代理類
        IDefender accuser = new Accuser();
        accuser.defend();

        DynamicProxy proxy = new DynamicProxy(accuser);
        ClassLoader loader = accuser.getClass().getClassLoader();
        IDefender proxyIDefender = (IDefender) Proxy.newProxyInstance(loader, new Class[]{IDefender.class}, proxy);

        proxyIDefender.defend();

    }
}

  這個客戶端的輸出結果,和之前是一樣的,可以發現,我們將之前代理類的工作,轉換到 InvocationHandler 的 invoke() 方法去執行,不再需要關心到底需要代理誰。

動態代理在 Retrofit 框架中的運用

  Retrofit 是 Android 上流行的 Http Client請求庫先看以下一段代碼

interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  List<Contributor> repoContributors(
      @Path("owner") String owner,
      @Path("repo") String repo);
}
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
//代理模式的運用
GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.repoContributors("owner","repo");

  由於我們要研究的方向是動態代理模式,所以我們直接深入主題,看一下這段代碼

GitHubService service = retrofit.create(GitHubService.class);

  GitHubService 是個接口,它作爲Retrofit.create()方法的參數傳入,這個方法的調用的返回對象 是個 GitHubService 的實例,那麼它是怎麼實現的呢?以下是 create()方法的代碼,看沒看到 Proxy.newProxyInstance() ,和 InvocationHandler()。同樣,在這裏 Retrofit 通過 註解 和 動態代理,用戶只需要使用 註解 作用在抽象方法和抽象方法的參數上申明,這些 註解、方法參數、 方法返回值類型就提供了一次網絡請求所需的信息,而具體的操作是由Retrofit通過解析這些信息,在運行期生成代理對象去調用。

  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, 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);
            }
            return loadMethodHandler(method).invoke(args);
          }
        });
  }

動態代理模式總結

  動態代理模式在代碼的運行階段才生成 代理類對象,動態代理模式運用在需要對訪問做特殊處理,比如對某個方法的調用加入權限驗證;或者是對原來的方法進行統一的拓展,比如加入日誌記錄等,代理模式還被運用在實現 AOP ,大家可以去了解一下。
  
  本文參考 :codekk-公共技術點之動態代理
  

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