设计模式-代理模式

2019-09-26  本文已影响0人  小的橘子

代理模式介绍

代理模式(Proxy Pattern)也称为委托模式,是结构型设计模式的一种。在现实生活中用到代理的场景有很多,如:加盟商,去代售点买票,代理上网等。

代理模式定义

为其他对象提供一种代理以控制这个对象的访问。

代理模式使用场景

当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理类对象来间接访问。

代理模式 UML 类图


角色介绍:

代理模式简单实现

这里以邮递物品为例,邮递公司看做代理(其不负责邮递),邮递员为实际邮递者。

抽象主题类(Subject)

抽象主题类具有真实主题类和代理的共同接口方法,需要邮递物品,则就是邮递

public interface IPost {
    void post(String goods);
}

真实主题类(RealSubject)

真正负责邮递物品的是快递员 Postman

public class Postman implements IPost {

    @Override
    public void post(String goods) {
        System.out.println("邮递员:邮寄物品为" + goods);
    }
}

代理类(ProxySubject)

起代理作用的是邮递公司,邮递公司并不进行实际邮递工作,其会让邮递员进行邮递,为了避免物品给邮递员带来危险,其负责对物品进行安全检查。

public class PostProxy implements IPost {

    private Postman mPostman;

    public PostProxy(Postman postman) {
        mPostman = postman;
    }

    @Override
    public void post(String goods) {
        System.out.println("货物安检");
        if (goods.contains("handgun")) {
            System.out.println("货物存在违禁品,无法邮递");
            return;
        }
        System.out.println("货物正常,开始邮递");
        mPostman.post(goods);
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        Postman postman = new Postman();
        PostProxy postProxy = new PostProxy(postman);
        // 邮递手机
        postProxy.post("handset");
        // 邮递手枪
        postProxy.post("handgun");
    }
}

输出结果如下:

货物安检
货物正常,开始邮递
邮递员:邮寄货物为handset

货物安检
货物存在违禁品,无法邮递

代理 PostProxy 完成了物品的安检和邮递。这里先说下我的疑问:已经有Postman 对象了,为什么又去调用 PostProxy?
我们的代理类可以不只为一个真实主题类做代理,我们可以为多个具有邮递能力的机构做代理,那么我们就可以将 PostProxy 持有 Postman 对象改为 IPost 接口。

代理模式的扩展

设计模式中有普通代理强制代理的概念。

普通代理示例

基于上述示例稍作修改:

public class Postman implements IPost {
    public Postman(IPost post) throws Exception {
        // 通过是否传入代理类来验证创建方
        if (post == null) {
            throw new Exception("不能创建真实主题角色");
        }
    }

    @Override
    public void post(String goods) {
        System.out.println("邮递员:邮寄货物为" + goods);
    }
}
public class PostProxy implements IPost {

    private Postman mPostman;

    public PostProxy() {
        try {
            // 有权创建 Postman
            mPostman = new Postman(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void post(String goods) {
        System.out.println("货物安检");
        if (goods.contains("handgun")) {
            System.out.println("货物存在违禁品,无法邮递");
            return;
        }
        System.out.println("货物正常,开始邮递");
        mPostman.post(goods);
    }
}
public class Client {
    public static void main(String[] args) {
        IPost post = new PostProxy();
        post.post("handset");
    }
}

运行结果完全相同。在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响。当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色,这也是一个非常好的方案。

强制代理示例

该种代理要求必须通过真实角色查找到代理角色,否则你无法访问。无论你是通过代理类还是通过直接new一个主题角色类,都无法访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色。以明星和他的经纪人为例,明星相当于真实角色,经纪人相当于代理,而你和该明星关系非常好,找他办事肯定直接给他打电话,明星最后就找他指定的经纪人完成你求的事。

public interface IPost {
    void post(String goods);
    // 都可以有自己指定的代理
    IPost getProxy();
}
public class Postman implements IPost {
    private IPost mProxy;

    @Override
    public void post(String goods) {
        // 是我的代理邮递,不是则提示
        if (isProxy()) {
            System.out.println("邮递员:邮寄货物为" + goods);
        } else {
            System.out.println("请找我的代理进行邮寄");
        }
    }
    
    // 返回我指定的代理
    @Override
    public IPost getProxy() {
        mProxy = new PostProxy(this);
        return mProxy;
    }
    // 判断是不是我的代理
    private boolean isProxy() {
        return mProxy != null;
    }
}
public class PostProxy implements IPost {
    // 被代理的对象
    private IPost mPoster;

    public PostProxy(IPost post) {
        mPoster = post;
    }
    @Override
    public void post(String goods) {
        mPoster.post(goods);
    }
    // 自己也可以被代理,我为自己代理。
    @Override
    public IPost getProxy() {
        return this;
    }
}

客户端代码

public class Client {
    public static void main(String[] args) {
        // 1. 直接通过真实对象邮递,邮递失败
        IPost post = new Postman();
        post.post("handset");
        // 2. 自己寻找邮递代理,邮递失败
        IPost proxy = new PostProxy(post);
        proxy.post("handset");
        // 3. 访问邮递员指定的代理,邮递成功
        post.getProxy().post("handset");
    }
}

输出结果:

请找我的代理进行邮寄
请找我的代理进行邮寄
邮递员:邮寄货物为handset

强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要自己创建一个代理出来,代理的管理已经由真实角色自己完成。

代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。

动态代理模式简单实现

从编码的角度来说,代理模式分为静态代理动态代理,上面的例子是静态代理,在代码运行前就已经存在了代理类的class编译文件,而动态代理则是在代码运行时通过反射来动态的生成代理类的对象,并确定到底来代理谁。也就是我们在编码阶段不需要知道代理谁,代理谁我们将会在代码运行时决定。Java 给我们提供了一个便捷的动态代理接口 InvocationHandler,实现该接口需要重写invoke()方法。下面我们在上面静态代理的例子上做修改:

创建动态代理类:

public class DynamicProxy implements InvocationHandler {

    // 被代理的类引用
    private Object obj;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(obj, args);
        return result;
    }
}

当调用代理类的接口方法时,会调用到 invoke 方法,该方法中负责反射调用被代理者的真实逻辑实现的方法。

修改客户端代码

public class Client {
    public static void main(String[] args) {
        IPost postman = new Postman();
        // 创建动态代理
        DynamicProxy dynamicProxy = new DynamicProxy(postman);
        // 创建被代理者的 ClassLoader
        ClassLoader loader = postman.getClass().getClassLoader();
        // 创建代理者
        IPost poster = (IPost) Proxy.newProxyInstance(loader, new Class[]{IPost.class}, dynamicProxy);
        poster.post("handgun");
    }
}

输出结果:

邮递员:邮寄货物为handgun

动态代理通过一个代理类来代理 N 多个被代理者,其实质是对代理者和被代理者解耦,使两者没有直接的耦合关系。 而静态代理则只对给定的接口下的实现类做代理,如果接口不同就需要重新定义代理类。

代理模式类型

从编码角度区分

从适用范围区分

静态代理和动态代理都可以应用于上述 4 中情形。

总结

代理模式优点
1.职责清晰:真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务
2.高扩展性:代理类可以完全控制真实主题,可以控制访问,扩展功能,而不用修改原真实主题。
代理模式缺点
1.由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2.实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

Android 源码中代理模式实现

Android 源码中有很多代理模式的实现,比如 AIDL 生成的Java文件中 Proxy类,这里直接以 Android 7.0 上 ActivityManagerProxy 为例介绍,ActivityManagerProxy 就相当于 AIDL 生成的 Proxy 类,只不过 Framework 中直接以 Java 代码的形式书写,在Android 8.0 上已经变化为 AIDL 形式,请知悉。

为了方便,下面对ActivityManagerProxy,ActivityManagerNative 和 ActivityManagerService 会以简写方式书写,分别为: AMP,AMN 和 AMS。
AMP 具体代理的是 AMN 的子类 AMS, AMP与 AMN 在同一文件中。

class ActivityManagerProxy implements IActivityManager {
  ...
}

IActivityManager 为接口类就是代理模式中的抽象主题,其中定义了一些 Activity 相关的接口方法

public interface IActivityManager extends IInterface {
  ...
  public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
            ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
  public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter,
            String requiredPermission, int userId) throws RemoteException;
  public int checkPermission(String permission, int pid, int uid)
            throws RemoteException;
  ...
}

对于真正的主题角色是谁呢?就是继承 AMN 的 AMS,这几个类大致关系如下图:


通过 UML 图可以清晰的看出 AMP 和 AMN 都实现了 IActivityManager 接口,严格地说,AMP 就是代理部分,而 AMN 就是真实主题角色,但 AMN 是抽象类,并不处理过多逻辑,大部分是由 AMS 完成。
AMS 是系统级Service,运行在system_server进程中,而 AMP 一般运行在客户端进程也就是 app 进程,他们之间的通信属于 Binder 跨进程通信。对于 AMP 并不是在 app 进程直接使用的,而是通过 ActivityManager 类,该类负责管理和维护 Activity 相关信息,但实际大多数逻辑是通过 AMP 承担。这里以 getMemoryInfo 为例介绍是如何工作的。
ActivityManager.java

public void getMemoryInfo(MemoryInfo outInfo) {
    try {
        ActivityManagerNative.getDefault().getMemoryInfo(outInfo);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

ActivityManagerNative.java
ActivityManagerNative.getDefault() 该方法实现如下

static public IActivityManager getDefault() {
    return gDefault.get();
}

gDefault 又是什么?

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        // 1. 与ServiceManger 进行 IPC 通信,获取系统级 Service,该Service 实质上是 AMS
        IBinder b = ServiceManager.getService("activity");
        // 2. 返回 AMP
        IActivityManager am = asInterface(b);
        return am;
    }
};
// 跨进程通信,则返回的是 AMP
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);
}

gDefault.get() 就是单例的 AMP 对象
ActivityManagerProxy.java

public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    // 1.
    mRemote.transact(GET_MEMORY_INFO_TRANSACTION, data, reply, 0);
    reply.readException();
    outInfo.readFromParcel(reply);
    data.recycle();
    reply.recycle();
}

该段就是 Binder 通信的核心代码,在注释1处,调用客户端Binder的 transact() 方法,服务端 Binder 的 onTransact() 就会回调,这个是由 Binder 驱动进行完成。客户端 transact 方法中传入方法标识为GET_MEMORY_INFO_TRANSACTION,data 用于向服务端写入数据,reply 用于接收服务端的返回数据,当服务端对应方法执行完后, transact 就停止阻塞,继续走到outInfo.readFromParcel(reply);,将返回结果写入outInfo 对象中。接下来我们看服务端是 AMS 是如何执行的?
onTransact 方法在 抽象类 AMN 中

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
    switch (code) {
    ...
    case GET_MEMORY_INFO_TRANSACTION: {
        data.enforceInterface(IActivityManager.descriptor);
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        getMemoryInfo(mi);
        reply.writeNoException();
        mi.writeToParcel(reply, 0);
        return true;
    }
    ...
}

实际是通过 getMemoryInfo 方法完成
ActivityManagerService.java

@Override
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
    final long homeAppMem = mProcessList.getMemLevel(ProcessList.HOME_APP_ADJ);
    final long cachedAppMem = mProcessList.getMemLevel(ProcessList.CACHED_APP_MIN_ADJ);
    outInfo.availMem = Process.getFreeMemory();
    outInfo.totalMem = Process.getTotalMemory();
    outInfo.threshold = homeAppMem;
    outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((cachedAppMem-homeAppMem)/2));
    outInfo.hiddenAppThreshold = cachedAppMem;
    outInfo.secondaryServerThreshold = mProcessList.getMemLevel(
            ProcessList.SERVICE_ADJ);
    outInfo.visibleAppThreshold = mProcessList.getMemLevel(
            ProcessList.VISIBLE_APP_ADJ);
    outInfo.foregroundAppThreshold = mProcessList.getMemLevel(
            ProcessList.FOREGROUND_APP_ADJ);
}

AMS 中完成获取 Memory 信息的获取,最后返回到客户端中调用时传入的 MemoryInfo 对象中。

客户端和具体主题角色 AMS 属于不同进程,所以这里的 AMP 就是远程代理,使系统可以将服务端的实现隐藏,客户端使用时无需关心 AMS。对于访问 AMS 都交由 AMP,由于牵扯到跨进程,调用 AMS 代码会比较复杂,这些都有 AMP 完成了处理,简化了客户端的访问。

上一篇下一篇

猜你喜欢

热点阅读