设计模式之代理模式

3,207 阅读10分钟
原文链接: blog.csdn.net

设计模式-代理模式


代理模式的概念

  代理模式(proxy pattern)是一种结构型的设计模式,代理模式在程序开发中的运用非常广泛。简单地描述代理模式就是:为其他对象(被代理对象)提供一种代理以控制对原有对象的操作。实际的行为是由被代理对象完成的
  代理模式可以分为两部分,静态代理动态代理,它们的区别将在下面详细介绍。

角色介绍

  Suject: 抽象主题类
  该类的主要职责是申明真是主题与代理的共同接口方法,该类既可以是个抽象类也可以是个接口(具有抽象方法)。
  RealSubject: 真实主题类
  该类也称为委托类或者被代理类,改类定义了代理所表示的真是对象(也就是实现了抽象方法),由其执行具体的业务逻辑。
  ProxySubject:代理类
  这个类的对象持有一个对真实主题的引用,在这个类所实现的接口方法中调用真实主题类中相应的方法执行,这样就实现了代理的目的。
  Client:客户类
  也就是使用代理类的类型,客户类通过代理类间接地调用了真实主题类中定义的方法。

代理模式的实现

简单的例子

针对,上方的角色介绍,举一个简单的例子:在现实的世界中,打公司一般有,原告 和原告的代理律师这样两个角色,他们要做的事情是 辩护。于是,我们可以实现以下几个类。
抽象主题类:interface IDefender,这个接口中有个 抽象方法 abstract public void defend(); 表示辩护这个行为。

public interface IDefender {
    abstract public void defend();//辩护行为
}

真实主题类: class Accuser,实现IDefender这个接口,具有具体的逻辑行为

  public class Accuser implements IDefender {

    @Override
    public void defend() {
        System.out.println("被告严重侵犯了公民的人身自由权...哔哩哔哩");
    }
}

代理类:AccuserProxy 类,原告请了个代理律师帮助它打官司,也就是AccuserProxy,由代理律师控制原告的表现

public class AccuserProxy implements IDefender {
    //持有被代理类的引用
    private IDefender iDefend;

    public AccuserProxy(IDefender iDefend) {
        this.iDefend = iDefend;
    }

    @Override
    public void defend() {
        beforeDefend();
        // 被代理类的行为
        iDefend.defend();
        afterDefend();
    }
    //修饰的方法
    private void beforeDefend(){
        System.out.print("我是原告律师,以下是我方辩词");
    }
    //修饰的方法
    private   void afterDefend(){
        System.out.print("辩护完毕");
    }
}

客户端:Client类,使用代理类的角色

public class Client {
    public static void main(String[] args) {
        Accuser accuser = new Accuser();
        accuser.defend();//此时输出 被告严重侵犯了公民的人身自由权...哔哩哔哩

        AccuserProxy accuserProxy = new AccuserProxy(accuser);
        accuserProxy.defend();
        // 输出:我是原告律师,以下是我方辩词,被告严重侵犯了公民的人身自由权...哔哩哔哩,辩护完毕

        /////////////////////////////////////////////////////
        // 因为,我们的代理类和被代理类 实现的是同一接口,如果将引用类型 写成IDfender,
        // 那么在调用 defend(),方法的使用 客户完全感觉不到被代理类的存在,当然因为我们这里的
        // 被代理类是通过构造函数传进去的,软件开发中,有时候直接在被代理类中实例化代理类,这样使用起来就更完美了。
        IDefender accuser2 = new Accuser();
        IDefender accuser2Proxy = new AccuserProxy(accuser2);

        accuser2.defend();
    }
}

  代理模式的运用符合开闭原则的定义,软件中的对象(类、模块、函数)应该对于拓展是开发的,对于修改是封闭的。我们不需要修改被代理类 (Accuser),使用代理模式就可以实现对原有功能的加强。以上代码只是一个简答的运用场景,现实开发中代理模式的运用非常广泛,它可以解决多方面的问题。

Androd 开发中的运用例子

  Android API的版本迭代很快,在每次的版本更新中 通常会加强一些原有的功能,对原有的类会增加新的接口,而每个版本能够调用的API 可能会都不同,比如 状态栏 Notification

Notfification可以分为4类,一类是正常视图,也就是我们通常在状态栏看到的 高度为 64dp 的长条状通知视图;一类是在 API16 中引入的以 Style 方式展示的 MediaStyleInboxStyleBigTextStyleBigPictureStyle 四种Notification 风格样式;一类也是在 API16 中引入的可以将通知视图显示为256dp 高度大视图的 bingContentView;最后一类是在 L 中引入的 headsUpContentVIew

  现在,我们的 App需要根据不同的版本实例化不同 Notification 显示不同的视图,在高版本显示的视图当然就更丰富,低版本更单调。如果你直接在 Activity 中判断版本,加上一坨的 switch 或者 if else 语句当然也是可以的。但是,我们现在来看一下 运用设计模式的思想,如何将代码抽离,对于客户端来说(Activity or Fragment),不应该关心这些细节。
以下的代码来自《Android源码设计模式解析与实战》一书。

抽象主题类: Notify类, Notify抽象类 声明了 NotificationManager 和NotificatoinCompat.Builder 2个成员变量来处理和通知的一些逻辑。在构造函数中初始化所有子类都会调用的逻辑方法。

public abstract class Notify {
    protected Context context;
    protected NotificationManager nm;
    protected NotificationCompat.Builder builder;

    public Notify(Context context){
        this.context = context;
        nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
        builder = new NotificationCompat.Builder(context);
        builder.setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(PendingIntent.getActivities(
                        context,
                        0,
                        new Intent[]{new Intent(context, NotifyActivity.class)},
                        PendingIntent.FLAG_UPDATE_CURRENT));
    }
    /**
     * 发送一条通知
     */
    public abstract void send();
    /**
     * 取消一条通知
     */
    public abstract void cancel();
}

真实主题类:NotifyNormal ,继承抽象主题类,简单重写抽象方法

public class NotifyNormal extends Notify{
    public NotifyNormal(Context context) {
        super(context);
    }

    @Override
    public void send() {
        Notification n = builder.build();
        n.contentView = new RemoteViews(context.getPackageName(),
                R.layout.remote_notify_proxy_normal);
        nm.notify(0,n);
    }

    @Override
    public void cancel() {
        nm.cancel(0);
    }
}

真实主题类 : 与NormalNotify的不同在于 还实现了bigContentView 的初始化,这是在 API 16以上才能调用的

public class NotifyBig extends Notify{

    public NotifyBig(Context context) {
        super(context);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void send() {
        Notification n = builder.build();
        n.contentView = new RemoteViews(context.getPackageName(), R.layout.remote_notify_proxy_normal);
        n.bigContentView = new RemoteViews(context.getPackageName(),R.layout.remote_notify_proxy_big);
        nm.notify(0,n);
    }

    @Override
    public void cancel() {
        nm.cancel(0);
    }
}

  源码中还有个 NotifyHeadUp 类,它的唯一区别就是 在bigContentVie 的基础上还能实现 Notification 的 headsUpContentView ,这个 View 是在 API 20以上 才能使用的,当我们的 App 以全屏的方式展开的时候如果收到了通知,这个 View 就会浮动展示于屏幕顶部。

  代理类: NotifyProxy ,持有被代理类对象,在这个示例中,我们根据不同的运行时环境 API 实例化不同的 被代理类对象。

public class NotifyProxy extends Notify{
    //代理类持有被代理类的引用
    private Notify notify;

    public NotifyProxy(Context context) {
        super(context);
        //根据版本实例化不同的被代理对象
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            notify = new NotifyHeadUp(context);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
            notify = new NotifyBig(context);
        } else {
            notify = new NotifyNormal(context);
        }
    }

    @Override
    public void send() {
        notify.send();
    }

    @Override
    public void cancel() {
        notify.cancel();
    }
}

  客户端类:也就是我们的Activity,在 Activity 中,我们直接将 Context 传入,代理类会帮我们实例化合适的被代理对象。

new NotifyProxy(NotifyActivity.this).send();

  在这个实例中,我们通过代理模式 ,使用一个代理类来针对不同的运行时系统版本,实例化不同的 Notificaition 的子类,而在客户端中 简单地调用代理类的send方法就可以。

静态代理模式总结

  代理模式通过代理类对外部提供统一的接口,在代理类中实现对被代理类的附加操作,从而可以在不影响外部调用的情况下实现系统的拓展,我觉得代理模式可能在一个程序项目的开发初期运用不到,而在项目成型而又有了新的变化、升级等,可以考虑用代理模式来实现,这样可以不需要修改原有的代码。

动态代理模式

  其实上文所讲述的内容只是代理模式的一部分,代理模式还有更为强大的动态代理模式。以下是这 2 个的区别:

静态代理模式:在我们的代码运行前,代理类的class编译文件就已经存在了
动态代理模式:在 code 阶段并不存在被代理类,而且并不知道要代理哪个对象,利用 Java 的反射机制在运行期动态地生成代理者的对象,代理谁将会在代码执行阶段决定。

动态代理模式的实现

  Java 已经为我们提供了一个便捷的动态代理接口 InvocationHandler ,我们重写其调用方法 invoke。以前面 我们的代理律师的例子为例,看看具体的操作是怎么样的

public class DynamicProxy implements InvocationHandler{
    private Object object;// 被代理类的类引用


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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeDefend();
        //调用被代理类对象的方法
        Object result = method.invoke(object, args);
        afterDefend();
        return result;
    }

    private void beforeDefend(){
        System.out.print("我是原告律师,以下是我方辩词");
    }
    private   void afterDefend(){
        System.out.print("辩护完毕");
    }
}

  解释一下这个 invoke 方法,我们通过 invoke 方法来调用具体的被代理方法,也就是真实的方法,如果对反射机制了解的话, method.invoke(object,args) 这句代码应该很好理解,object 是被代理类,method 是被代理类的方法,args 是方法的参数。

  我们仅仅是实现了 InvocationHandler 的接口,那么接下来该做什么呢?怎么实现动态生成代理者对象呢?Java 的 java.lang.reflect 包下 还有一个 Proxy 类,它有个静态方法 newProxyInstance(),我们使用这个方法生成。以前面 我们的代理律师的例子为例,

public class Client {
    public static void main(String[] args) {
        //被代理类
        IDefender accuser = new Accuser();
        accuser.defend();

        DynamicProxy proxy = new DynamicProxy(accuser);
        ClassLoader loader = accuser.getClass().getClassLoader();
        IDefender proxyIDefender = (IDefender) Proxy.newProxyInstance(loader, new Class[]{IDefender.class}, proxy);

        proxyIDefender.defend();

    }
}

  这个客户端的输出结果,和之前是一样的,可以发现,我们将之前代理类的工作,转换到 InvocationHandler 的 invoke() 方法去执行,不再需要关心到底需要代理谁。

动态代理在 Retrofit 框架中的运用

  Retrofit 是 Android 上流行的 Http Client请求库先看以下一段代码

interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  List<Contributor> repoContributors(
      @Path("owner") String owner,
      @Path("repo") String repo);
}
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
//代理模式的运用
GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.repoContributors("owner","repo");

  由于我们要研究的方向是动态代理模式,所以我们直接深入主题,看一下这段代码

GitHubService service = retrofit.create(GitHubService.class);

  GitHubService 是个接口,它作为Retrofit.create()方法的参数传入,这个方法的调用的返回对象 是个 GitHubService 的实例,那么它是怎么实现的呢?以下是 create()方法的代码,看没看到 Proxy.newProxyInstance() ,和 InvocationHandler()。同样,在这里 Retrofit 通过 注解 和 动态代理,用户只需要使用 注解 作用在抽象方法和抽象方法的参数上申明,这些 注解、方法参数、 方法返回值类型就提供了一次网络请求所需的信息,而具体的操作是由Retrofit通过解析这些信息,在运行期生成代理对象去调用。

  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, 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);
            }
            return loadMethodHandler(method).invoke(args);
          }
        });
  }

动态代理模式总结

  动态代理模式在代码的运行阶段才生成 代理类对象,动态代理模式运用在需要对访问做特殊处理,比如对某个方法的调用加入权限验证;或者是对原来的方法进行统一的拓展,比如加入日志记录等,代理模式还被运用在实现 AOP ,大家可以去了解一下。
  
  本文参考 :codekk-公共技术点之动态代理