代理模式

介绍

代理模式也称为委托模式,是一种结构性设计模式。

说到代理,可能大部分人都会有一种陌生又熟悉的感觉,日常生活中好像都能遇到,比如代理上网,招商代理,商务代理等;但又说不出个具体的一二三来;代理这个事情如果我们换个角度,从委托者的角色出发,我们找代理上网,是因为我们在访问某些网站时存在困难,需要有个角色来间接的帮我们实现这个功能;我们找商务代理,可能是因为许多事我们不在行或者其他原因,需要找专业的中间人来帮我们做事。因此,日常生活中我们更多扮演的是委托人的角色,代理以一种中间人的角色,帮我们是处理我们无能为力的事情。

如果从写代码的角度出发,当我们遇到以下场景:

  • 无法直接访问某个对象
  • 不想直接访问某个对象
  • 访问某个对象存在困难

的时候,我们就可以通过一个代理,通过它来间接访问真正的对象。

定义及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发起网络请求了。

总结

以上就是对代理模式的分析,总的来说代理模式的结构非常简单;包括抽象主题,委托类,代理类三个核心角色,从大的方向上可以分为静态代理和动态代理两大类;通过静态代理的方式,在开发迭代的过程中,为实现兼容性提供了一种非常友好的实现思路;在日常开发中,如果我们使用的对象之间有着强烈的耦合,可是思考一下是否可以通过代理模式解耦;同时,当我们需要扩展某个类的部分功能时,但又不想去破坏原有的功能或者是根本无法修改时,我们可以考虑代理模式,但也要明白,通过代理模式我们能做的也只能是功能扩展,想要更新委托类中已经实现的内容他是无能为力的。

动态代理,可以根据运行时的委托类动态的生成代理类,这样就减轻了代理类的负担,避免在编码阶段就具体的委托类再做各种判断了。

代理模式很简单,也很实用,但不要忘记代理类委托类需要实现功能的接口或抽象类,不要忽略了这一点。

好了,关于代理模式的分析就到这里了


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容