当Android遇上设计模式之代理(Proxy)模式

设计模式六大原则:

  • 单一职责原则:就一个类仅有一个引起它变化的原因,即类承担的职责单一性;

  • 开放封闭原则:类、模块、函数等应该是可以扩展的,但是不可修改。换句话说,就是面对需求的改变要尽可能地保证相对稳定,尽量通过扩展的方式而不是修改原有的代码来实现。

  • 里氏替换原则:所有引用基类(父类)的地方必须透明地使用其子类对象。换句话说,就是尽量把父类设计为抽象类或者接口,在运行时子类实例替换父类实例,在扩展时通过增加一个新的子类实现;

  • 依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖于抽象。换句话说,就是模块间的依赖通过抽象发生,实现类之间不发生直接依赖关系,其依赖关系是通过接口或者抽象类产生。

  • 迪米特原则:一个软件实体应当尽可能少地与其他实体发生相互作用。换句话说,就是通过引入一个合理的第三者来降低现有对象之间的耦合度,同时在设计类时尽量降低成员的访问权限。

  • 接口隔离原则:一个类对另一个类的依赖应该建立在最小接口上。换句话说,就是为各个类建立专用的接口,而不要试图建立一个庞大的接口供所有依赖它的类调用,同时接口中的方法尽量少且少用public修饰,以提高内聚和减少对外交互。

1. 代理模式

 代理模式又称委托模式,它是结构型模式的一种,所谓结构型模式,是指从程序的结构上解决模块之间的耦合问题。在现实生活中,类似代理模式的场景非常多,比如代购、代理上网等,它的定义为:为其他对象提供一种代理以控制对这个对象的访问。换句话来说,就是通过代理来实现对某个对象的访问。代理模式UML类图如下:
在这里插入图片描述
 在代理模式中有如下角色:

  • Subject:抽象主体类,它负责声明真实主体与代理的共同接口方法;
  • RealSubject:真实主题类,继承于Subject,是代理类所代表的真实主题;
  • Proxy:代理类,继承于Subject,持有对真实主题类的引用。客户端就是通过这个代理类间接地调用真实主题类的方法。

1.1 代码实现

 在Android开发中,代理模式是一种较为常见的设计模式,比如基于Binder的跨进程通信机制就是基于代理模式实现的,其中,IInterface接口就是代码模式中的Subject,它声明了一些公共方法;远程服务端的Binder实体对象就是代理模式中的RealSubject,它继承于IInterface且实现了这些公共方法;本地代理对象BinderProxy就是Proxy,它也继承于IInterface但是没有实现这些公共方法,而是持有远程服务端Binder实体对象的引用。接下来,我们就模拟这种Binder机制来了解代理模式的实现思路。

(1)IInterface,抽象主体类

/** 抽象主体类
 * author : jiangdg
 * date   : 2020/1/31 10:23
 * desc   : 声明真实主题和代理的共同接口方法
 *  本例为将加运算放在远程服务端实现
 * version: 1.0
 */
public interface IInterface {
    int add(int x, int y);
}

(2)Binder,真实主题类

/** 真实主题类
 * author : jiangdg
 * date   : 2020/1/31 10:23
 * desc   : 为IInterface的中的方法具体实现
 * 本例中表示远程Binder实体,是真正的实现
 * version: 1.0
 */
public class Binder extends IInterface {
    @Override
    public int add(int x, int y) {
        return x + y;
    }
}

(3)BinderProxy,代理类

/** 代理类
 * author : jiangdg
 * date   : 2020/1/31 10:23
 * desc   : 持有远程Binder实体对象的引用,客户端就是通过这个代理对象
 *  实现对远程Binder对象的访问
 * version: 1.0
 */
public class BinderProxy extends IInterface {
    private IInterface mBinder;
    
    public BinderProxy(IInterface binder) {
        mBinder = binder;
    }
    
    @Override
    public int add(int x, int y) {
        // 调用远程Binder的add方法实现加运算
        return mBinder.add(x, y);
    }
}

 接下来,我们就来看客户端如何通过BinderProxy代理类实现对远程Binder对象的方法的访问,相关代码如下:

// 创建代理类,需要传入远程Binder对象
// 这个过程即为为远程Binder对象创建代理
IInterface proxy = new BinderProxy(new Binder());
// 执行加运算。
// 虽然调用的是代理类的add方法,但是实际运算操作是在远程Binder对象的add方法
int result = proxy.add(1, 2)

注:实际上Binder机制远比上述描述的复杂,本文只是为了演示代理模式,如果想进一步了解基于Binder机制的IPC通信,可看我这篇博文《从Android6.0源码的角度剖析Binder工作原理

1.2 使用场景

 代理模式的优点在于使得模块之间真正的解耦,即真实主题类RealSubject只负责实现实际的业务逻辑,不用关系其他非本职的工作,当真实主题类发生变化时,由于它和代理类均实现了公共接口,因此代理类Proxy可以不做任何修改就能够使用。基于此,代理模式主要有以下几种使用场景:

  • 远程代理

 为一个对象在不同的地址空间提供局部代表,这样系统可以将Server部分的实现隐藏;

  • 虚拟代理

 使用一个代理对象表示一个十分耗费资源的对象并在真正需要时才创建;

  • 安全代理

 用来控制真实对象访问时的权限。一般用于真实对象有不同的访问权限场景。

2. 静态代理与动态代理

 从编码的角度来说,代理模式分为静态代理和动态代理。在第一小节中演示的例子就是静态代理,即在代码运行前就已经存在了代理类的class编译文件,换句话来说,就是在代码运行前我们就知道BinderProxy是Binder的代理;而动态代理是指在代码运行前我们并不知道谁来代理Binder对象,而是需要在代码运行时通过反射来动态地生成代理类的对象,也就是说,我们在编码阶段无需知道代理谁,代理谁将会在代码运行时决定。Java提供了动态地代理接口InvacationHandler,实现该接口需要重写invoke方法。下面我们将第一小节的案例改为动态代理,具体步骤如下:

(1)创建动态代理类

public class DynamicCalculate implements InvacationHandler {
    // 指向被代理的对象
    private Object obj;
    
    public DynamicCalculate(Object obj) {
        this.obj = obj;
    }
    
    // 重写invoke方法
    // 通过反射调用被代理对象的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
    		throws Throwable{
        Object result = method.invoke(obj, args);
        return result;
    }
}

(2)客户端动态创建Binder的代理类

public class Client {
    public static void main(String[] args) {
        IInterface mBinder = new Binder();
        // 1. 创建动态代理
        // 需要传入远程Binder对象
        DynamicCalculate dynamicCalculate = new DynamicCalculate(mBinder);
        // 2. 动态创建Binder对象的代理类
        // 需要传入Binder类的ClassLoader
        ClassLoader loader = mBinder.getClass().getClassLoader();
        IInterface binderProxy = (IInterface)Proxy.newProxyInstance(loader, new Class[]
                                           {IInterface.class}, dynamicCalculate);
        // 3. 通过代理访问远程Binder的方法
        int result = binderProxy.add(1, 2);
    }
}

静态代理中的代理类是在编码阶段确定的;动态代理中的代理类是在运行时确定的。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页