设计模式解析之代理模式

来源:互联网 发布:php高并发秒杀 实例 编辑:程序博客网 时间:2024/05/01 01:46

设计模式-代理模式


代理模式的概念

  代理模式(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-公共技术点之动态代理
  

1 0
原创粉丝点击