0%

代理模式

介绍

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

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

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

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

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

定义及UML图

定义:

为目标对象提供一种代理,客户端通过代理去访问目标对象。

UML 图

从代理模式的UML 类图中,我们可以得到如下结论:

  • 代理对象和委托对象需要实现相同的接口(抽象类)
  • 代理对象持有委托对象的引用

可以看到,代理模式非常简洁,总共就三个角色,包括抽象主题,委托者和代理者。下面用代码简单实现一下基础的代理模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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()

1
2
3
4
5
6
7
  @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
……
}

可以看到这里他并没具体去实现onCreate,而是使用一个AppCompatDelegate实例的onCreate()方法去实现。继续看getDelegate 的实现。

AppCompatActivity-getDelegate()

1
2
3
4
5
6
7
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}

可以看到这个实例创建是在AppCompatDelegate类中。接着看create的实现

AppCompatDelegate-create()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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了。这里可以简单看一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

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方法时,到底发生了什么呢?

```java
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发起网络请求了。

总结

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

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

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


加个鸡腿呗.