java/android 设计模式学习笔记(9):代理模式

这篇博客我们来介绍一下代理模式(Proxy Pattern),代理模式也成为委托模式,是一个非常重要的设计模式,不少设计模式也都会有代理模式的影子。代理在我们日常生活中也很常见,比如上网时连接的代理服务器地址,更比如我们平时租房子,将找房子的过程代理给中介等等,都是代理模式在日常生活中的使用例子。
代理模式中的代理对象能够连接任何事物:一个网络连接,一个占用很多内存的大对象,一个文件,或者是一些复制起来代价很高甚至根本不可能复制的一些资源。总之,代理是一个由客户端调用去访问幕后真正服务的包装对象,使用代理可以很容易地转发到真正的对象上,或者在此基础上去提供额外的逻辑。代理模式中也可以提供额外的功能,比如在资源集中访问操作时提供缓存服务,或者在操作真正执行到对象之前进行前提条件的检查。对于客户端来说,使用代理对象,和使用真正的对象其实是类似的,因为他们都实现了同样的接口。
PS:对技术感兴趣的同鞋加群544645972一起交流。

设计模式总目录

java/android 设计模式学习笔记目录

特点

代理模式为另一个对象提供一个代理以控制对这个对象的访问。使用代理模式创建代理,让代理对象控制对某个对象的访问,被代理的对象可以是远程的对象,创建开销很大的对象,或者是需要安全控制的对象,所以代理模式的使用场景为:当无法或者不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端的透明性,委托对象与代理对象需要实现同样的接口。
代理模式可以大致分为静态代理和动态代理。静态代理模式的代码由程序员自己或通过一些自动化工具生成固定的代码再对其进行编译,也就是说我们的代码在运行前代理类的 class 编译文件就已经存在;而动态代理则与静态代理相反,在 Java 或者 Android 中通过反射机制动态地生成代理者的对象,也就是说我们在 code 阶段完全不需要知道代理谁,代理谁我们将会在执行阶段决定,在 Java 中,也提供了相关的动态代理接口 InvocationHandler 类,这个我们在后面源码的时候会用到。
代理模式根据实际使用的场景也可以分为以下几种:

  • 远程代理(Remote Proxy):为某个在不同的内存地址空间的对象提供局部代理,使系统可以将 Server 部分的实现隐藏,以便 Client 可以不必考虑 Server 的存在,类似于 C/S 模式(主要拦截并控制远程方法的调用,做代理防火墙之类的);
  • 虚拟代理(Virtual Proxy):使用一个代理对象标识一个十分耗资源的对象,并在真正需要时才创建,实现一个延迟加载的机制;
  • 保护代理(Protection Proxy):是用代理控制对原始对象的访问,该类型的代理通常被用于原始对象有不同访问权限的情况;
  • 智能引用(Smart Proxy):在访问原始对象时执行一些自己的附加操作并对指向原始对象的引用计数;
  • 写时拷贝(克隆)代理(Copy-on-write Proxy):其实是虚拟代理的一个分支,提供了拷贝大对象的时候只有在对象真正变化后才会进行拷贝(克隆)的操作,即延迟拷贝。

需要注意的一点是,静态和动态代理都可以应用于上述的几种情形,两者是各自独立变化的。

UML类图

我们来看看代理模式的 uml 类图:
这里写图片描述
据此我们可以写出代理模式的通用代码:
Subject.class

public abstract class Subject {
    public abstract void operation();
}

RealSubject.class

public class RealSubject extends Subject{
    @Override
    public void operation() {
        //the real operation
    }
}

ProxySubject.class

public class ProxySubject extends Subject{

    private Subject realSubject;

    public ProxySubject(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void operation() {
        if (realSubject != null) {
            realSubject.operation();
        } else {
            //do something else
        }
    }
}

客户端 Client 代码

ProxySubject subject = new ProxySubject(new RealSubject());
subject.operation();

代理模式的角色:

    • Subject:抽象主题类

该类的主要职责是声明真实主题与代理的共同接口方法,该类既可以是一个抽象类,也可以是一个接口;

    • RealSubjct:真实主题类

该类也称为被委托类或被代理类,该类定义了代理所表示的真实对象,由其执行具体的业务逻辑方法,而客户端则通过代理类间接地调用真实主题类中定义的方法;

    • ProxySubject:代理类

该类也称为委托类或代理类,该类持有一个对真实主题类的引用,在其所实现的接口方法中调用真实主题类中对应的接口方法,以此起到代理的作用;

  • Client:客户类,即使用代理类的部分。

Android 源码代理模式分析

Android 源码里有不少关于代理模式的实现,最典型的比如源码中的 ActivityManagerProxy 代理类,其具体代理的是 ActivityManagerNative 的子类 ActivityManagerService 类,对 AMS 感兴趣的可以去网上查阅一下相关资料,这里就不详细介绍了。我们老规矩,来看看 uml 图:
这里写图片描述
我在之前的一篇博客:android 不能在子线程中更新ui的讨论和分析 中分析了 Activity 的创建过程,感兴趣的可以去看看,在博客中分析到 startActivity 方法调用到了 Instrumentation 的 execStartActivity 方法, execStartActivity 方法又会调用 ActivityManagerNative.getDefault().startActivity 方法,最后调用到了 ActivityManagerService 中,但是当时并没有很明白为什么会调用到 AMS 中,所以这里根据上面的 uml 图补充说明一下。其实不光是 Instrumentation 类会调用到 ActivityManagerNative.getDefault() 方法,ActivityManager 中的很多方法都调用到了 ActivityManagerNative.getDefault(),比如 moveTaskToFront 方法:

public void moveTaskToFront(int taskId, int flags, Bundle options) {
    try {
        ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags, options);
    } catch (RemoteException e) {
        // System dead, we will be dead too soon!
    }
}

和 getRunningServices 方法等等等:

public List<RunningServiceInfo> getRunningServices(int maxNum)
        throws SecurityException {
    try {
        return ActivityManagerNative.getDefault()
                .getServices(maxNum, 0);
    } catch (RemoteException e) {
        // System dead, we will be dead too soon!
        return null;
    }
}

直接将函数调用转给了 ActivityManagerNative.getDefault() 方法,看看 getDefault() 函数:

public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
    /**
     * Cast a Binder object into an activity manager interface, generating
     * a proxy if needed.
     */
    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);
    }
...
    static public IActivityManager getDefault() {
        return gDefault.get();
    }
...
    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
...
}

getDefault 函数通过一个 Singleton 对象对外提供, ServiceManager.getService(“activity”) 返回的是 ActivityManagerService 的 Binder 对象,我们在看看 asInterface 函数,如果 queryLocalInterface 方法返回的是 null ,那么就会去创建一个 ActivityManagerProxy 对象,并且将 AMS 这个 Binder 对象作为参数传递给 ActivityManagerProxy 对象,来简单看看 ActivityManagerProxy 的代码:

class ActivityManagerProxy implements IActivityManager
{
    public ActivityManagerProxy(IBinder remote)
    {
        mRemote = remote;
    }

    public IBinder asBinder()
    {
        return mRemote;
    }

    public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        ...
        mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
        reply.readException();
        ...
    }
...
}

看了这里的代码之后就一目了然了,代理对象的函数比如 startActivity 方法最终调用到的还是 AMS 中,ActivityManagerProxy 的作用只是作为代理而已。调用到 AMS 的后续步骤可以回到博客: android 不能在子线程中更新ui的讨论和分析 去看看,这里就重复了。
还有几点需要在这里特殊说明一下:

    • 第一点:为什么 IActivityManager 需要实现 IInterface 接口,或者为什么 ActivityManagerProxy 需要继承 IInterface 接口的行为

我们看看 IInterface 接口的定义:

/**
 * Base class for Binder interfaces.  When defining a new interface,
 * you must derive it from IInterface.
 */
public interface IInterface
{
    /**
     * Retrieve the Binder object associated with this interface.
     * You must use this instead of a plain cast, so that proxy objects
     * can return the correct result.
     */
    public IBinder asBinder();
}

有一个 asBinder 的方法,而 ActivityManagerProxy 的 asBinder 方法实现在上面已经贴出代码了,所以说 ActivityManagerProxy 中的 asBinder 函数是用来返回 AMS 这个 Binder 对象的;

  • 第二点,继承 IBinder 和 Binder 的作用

我们知道 AMS 是由 SystemServer 来启动的,它是运行在另一个独立进程中的(这点和 WindowManagerService 类似,对 WMS 感兴趣的可以看看我的上篇博客: java/android 设计模式学习笔记(8):桥接模式),而 ActivityManagerProxy 是运行于当前进程当中的,此时必须要用到跨进程的通信,继承自 IInterface ,IBinder 接口和 Binder 类也是必然的(关于 IPC 通信,感兴趣的可以去看看我的博客:android IPC通信(下)-AIDL)。到这里,如果对 AIDL 熟悉的童鞋,就应该发现了其实这个 ActivityManagerProxy 和 ActivityManagerService 类就相当于我们写了一个 aidl 文件之后,系统为我们自动生成文件中的 Proxy 类和 Stub 类,所以说 aidl 就是一个使用远程代理模式非常实际的例子

  • 第三点,asInterface 方法的注释 “Cast a Binder object into an activity manager interface, generating a proxy if needed”,所以 if needed 是指的什么情况呢?

上面讲到 AIDL 文件生成的类中会自动生成两个类,Proxy 类和 Stub 类,对应的是 ActivityManagerProxy 和 ActivityManagerService 类,AMS 的初始化是在 SystemServer 进程中,所以 ActivityManagerNative 构造函数是在 SystemServer 进程中调用:

public ActivityManagerNative() {
    attachInterface(this, descriptor);
}

调用到 attachInterface 方法:

public void attachInterface(IInterface owner, String descriptor) {
    mOwner = owner;
    mDescriptor = descriptor;
}

而第二点中 gDefault 对象的 create 函数是在应用进程中调用的,这是两个不同的进程,所以我们再来看看 asInterface 函数:

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);
}

然后是 queryLocalInterface 函数:

public IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

所以最终这个函数是返回 null 的,也就是说跨进程调用的时候,会 new 一个 ActivityManagerProxy 对象。

示例与源码

看了上面的通用代码和 Android 源码的 AMS 机制之后,代理模式应该已经很清楚了,但是上面用到的都是静态代理,都是在编译阶段生成的 class 文件,所以这里以一个动态+保护代理模式的 demo 为例:

动态+保护代理

先看看它的 uml 类图:
这里写图片描述
这里我们用到了 InvocationHandler 这个类,这是 Java 为我们提供的一个便捷动态代理接口,作用就是用来在运行时动态生成代理对象,在这里就不详细介绍了,感兴趣的可以去查阅相关资料(http://paddy-w.iteye.com/blog/841798)。其次是保护代理,这里就以 ProxyA 和 ProxyB 两个代理类为例,一个只能调用 operationA 方法,另一个只能调用 operationB 方法。首先来看看 Subject 和 RealSubject 类:
Subject.class

public interface Subject {

    String operationA();

    String operationB();
}

RealSubjct.class

public class RealSubject implements Subject{
    @Override
    public String operationA() {
        return "this is operationA";
    }

    @Override
    public String operationB() {
        return "this is operationB";
    }
}

定义完了抽象主题类和真实主题类之后,开始构造代理类,首先是代理类的虚基类:
ISubjectProxy.class

public abstract class ISubjectProxy implements InvocationHandler {
    protected Subject subject;
    public ISubjectProxy(Subject subject) {
        this.subject = subject;
    }
}

然后是 ProxyA 和 ProxyB 这两个代理子类:
ProxyA.class

public class ProxyA extends ISubjectProxy{
    public ProxyA(Subject subject) {
        super(subject);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("operationB")){
            throw new UnsupportedOperationException("ProxyA can't invoke operationB");
        }else if (method.getName().equals("operationA")) {
            return method.invoke(subject, args);
        }
        return null;
    }
}

ProxyB.class

public class ProxyB extends ISubjectProxy{
    public ProxyB(Subject subject) {
        super(subject);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("operationA")){
            throw new UnsupportedOperationException("ProxyB can't invoke operationA");
        } else if (method.getName().equals("operationB")) {
            return method.invoke(subject, args);
        }
        return null;
    }
}

最后是 Client 的代码:

Subject subject = new RealSubject();
ISubjectProxy proxy = null;
switch (v.getId()) {
    case R.id.proxy_a:
        proxy = new ProxyA(subject);
        break;
    case R.id.proxy_b:
        proxy = new ProxyB(subject);
        break;
}
Subject sub = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
        subject.getClass().getInterfaces(), proxy);
try {
    Log.e("Shawn", sub.operationA());
}catch (UnsupportedOperationException e){
    Log.e("Shawn", e.getMessage());
}
try {
    Log.e("Shawn", sub.operationB());
}catch (UnsupportedOperationException e){
    Log.e("Shawn", e.getMessage());
}

结果:

com.android.proxypattern E/Shawn: this is operationA
com.android.proxypattern E/Shawn: ProxyA can't invoke operationB
com.android.proxypattern E/Shawn: ProxyB can't invoke operationA
com.android.proxypattern E/Shawn: this is operationB

代码很简单,第一点是使用 InvocationHandler 实现了动态代理,第二点是根据 method 的名字实现了保护代理。
这里只总结了保护代理,虚拟代理这里简单举个例子: moduleA 为 modleB 的 lib,所以如果在 moduleA 中需要用到 moduleB 实现的对象,就可以使用代理模式,具体步骤是在 moduleA 中定义接口,moduleB 去实现该接口,moduleA 中定义一个代理对象并提供一些默认操作,当 moduleB 的相关模块初始化之后,将该对象设置到 moduleA 的代理对象中以替代原来的默认实现,之后代理对象就能成功调用 moduleB 中定义的行为了,而且也实现了延迟加载。远程代理和其他的代理模式大家去网上查阅一下相关资料,万变不离其宗,道理是一样的。

总结

代理模式应用广泛,超级广泛,很多设计模式都会有代理模式的影子,有些模式单独作为一种设计模式,倒不如说是对代理模式的一种针对性优化,而且代理模式的缺点也很少,总结一下代理模式的优缺点:

  • 优点:
    1. 代理作为调用着和真实对象的中间层,降低了模块间和系统的耦合性;
    2. 可以以一个小对象代理一个大对象,达到优化系统提高运行速度的目的;
    3. 提供 RealSubject 的权限管理;
    4. 容易扩展,RealSubject 和 ProxySubject 都接口化了,RealSubject更改业务后只要接口不变,ProxySubject 可以不做任何修改。
  • 缺点:
    1. 调用者和真实对象多了一个中间层,对象的创建,函数调用使用的反射等都增加调用响应的时间;
    2. 设计模式的通病:类的增加,不过这点也不严重。

 

适配器 VS 装饰者 VS 桥接 VS 代理 VS 外观

这几个都是结构型设计模式,他们有些类似,在实际使用过程中也容易搞混,我们在这就给他们做一个对比:

适配器模式

适配器模式和其他三个设计模式一般不容易搞混,它的作用是将原来不兼容的两个类融合在一起,uml 图也和其他的差别很大。
uml 类图:
这里写图片描述

装饰者模式

装饰者模式结构上类似于代理模式,但是和代理模式的目的是不一样的,装饰者是用来动态地给一个对象添加一些额外的职责,装饰者模式为对象加上行为,而代理则是控制访问
uml 类图:
这里写图片描述

桥接模式

桥接模式的目的是为了将抽象部分与实现部分分离,使他们都可以独立地进行变化,所以说他们两个部分是独立的,没有实现自同一个接口,这是桥接模式与代理模式,装饰者模式的区别
uml 类图:
这里写图片描述

代理模式

代理模式为另一个对象提供代表,以便控制客户对对象的访问,管理的方式有很多种,比如远程代理和虚拟代理等,这个在上面有,这里就不说了,而装饰者模式则是为了扩展对象。
uml 类图:
这里写图片描述

外观模式

外观模式提供一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
适配器模式将一个或多个类接口变成客户端所期望的一个接口,虽然大多数资料所采用的例子中适配器只适配一个类,但是你可以适配许多类来提供一个接口让客户端访问;类似的,外观模式 也可以只针对一个拥有复杂接口的类提供简化的接口,两种模式的差异,不在于他们“包装”了几个类,而是在于它们的意图。适配器模式 的意图是,“改变”接口符合客户的期望;而外观模式的意图是,提供子系统的一个简化接口。
uml类图:
这里写图片描述

源码下载

https://github.com/zhaozepeng/Design-Patterns/tree/master/ProxyPattern

引用

http://blog.csdn.net/jason0539/article/details/22974405
https://en.wikipedia.org/wiki/Proxy_pattern
http://blog.csdn.net/l2show/article/details/46992495
http://paddy-w.iteye.com/blog/841798

本系列:



可能感兴趣的文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部