23种设计模式探索之代理模式

Posted 卓诺不迷路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了23种设计模式探索之代理模式相关的知识,希望对你有一定的参考价值。


前段时间加班赶项目,忙的飞起,好在如期出版本了,这些天的辛苦也算没白费。很快又要开始忙起来了,趁着这个间隙,给大家分享一下我对设计模式的理解。今天就从使用最广的代理模式开始。


23种设计模式探索之代理模式


什么是代理模式

    代理模式也称委托模式,是结构型模式的一种。简单讲就是,给某一对象提供一个代理对象,由代理对象控制外部对原对象的访问,并决定原对象的动作。

    举个例子。我是一个程序员,我在给客户在做一个项目,客户提需求,我开发需求。但是呢,客户提的需求可能是模糊的、不合理的。我作为一个程序员是很忙的,不想、也没时间跟客户BB,怎么办呢?我可以委托项目经理去跟客户对接,梳理、过滤客户需求,确定那些需求需要开发、哪些需求不用开发。而我,只管接受项目经理给我的开发任务就行了。

    上面这个例子中,项目经理就相当于代理类(也称委托类),我相当于被代理类(也称被委托类),我们的共同目的是做好这个项目(抽象主题)。


代理模式分类

    通常我们根据代理类class文件被创建的时间,将代理模式分为静态代理、动态代理两大类。

    静态代理,在程序编译的时候,代理类.class文件就已经被创建了。也就是说,在代码块执行之前,就知道是代理谁了。

    动态代理,在代码块运行的时候,通过反射来动态的生成代理类.class文件及代理类的对象,并确定代理谁。

    下面讲讲两种代理模式的具体实现,仍然以项目经理-程序员为例。


静态代理

    在这个例子中,项目经理(代理类)和程序员(被代理类)有着共同的目的(抽象主题)——做项目。我们可以抽象出一个共同的业务接口IDoProject,代理类ProjectManagerLuffy和被代理类ProgrammerZoro分别实现这个业务接口。

    不多BB,直接贴代码。

1,抽象主题类IDoProject.java

public interface IDoProject {    void work();}

2,被代理类(程序员卓诺)ProgrammerZoro.java

public class ProgrammerZoro implements IDoProject{
@Override public void work() {        Log.d("fxp""程序员Zoro开发需求..."); }}

3,代理类(项目经理路飞)ProjectManagerLuffy.java

public class ProjectManagerLuffy implements IDoProject{
private ProgrammerZoro zoro;
public ProjectManagerLuffy(ProgrammerZoro zoro){ this.zoro = zoro; }
@Override public void work() { // 整理需求 Log.d("fxp", "项目经理Luffy整理需求...");
// 评估需求是否合理 boolean isReasonable = (new Random()).nextBoolean(); Log.d("fxp", "项目经理Luffy评估需求是否合理...");
if (isReasonable){ // 需求合理-提交给程序员 Log.d("fxp", "需求评估结果为合理,项目经理Luffy将需求提交给程序员Zoro...");
zoro.work(); } else { // 需求不合理-驳回 Log.d("fxp", "需求评估结果为不合理,项目经理Luffy不将需求提交给程序员Zoro"); } }}

4,客户端

 private void commitNeedsToLuffy(){        Log.d("fxp""客户将需求提交给项目经理Luffy...");        ProjectManagerLuffy luffy = new ProjectManagerLuffy(zoro); luffy.work(); }

运行结果:

23种设计模式探索之代理模式

23种设计模式探索之代理模式


    静态代理中,代理类和被代理类关系太密切了。按照静态代理的实现方式,如果有多个项目经理,我们就得创建多个代理类ProjectManager1、ProjectManager2...。N个项目经理就得创建N个类,这显然是不合适的。而且,所有的代理类都需要实现与被代理类的共同业务接口,一旦共同业务接口增加方法,所有的代理类都得维护,这显然也是不合适的。

    使用动态代理可以解决这些问题。


动态代理

    动态代理又分JDK代理(也称接口代理)和CGLIB代理,今天我们只聊JDK代理。JDK代理特点:调用Proxy类的newProxyInstance方法,通过反射动态的生成代理对象。

    下面我们一起看看Proxy类的newProxyInstance方法源码。当然,我已经删除了废弃部分,并手动添加了中文注释讲解:

 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {        Objects.requireNonNull(h);        // 克隆一份业务接口        final Class<?>[] intfs = interfaces.clone();        // 调用getProxyClass0()方法,生成一个实现了业务接口的class字节码对象cl(将类加载器和克隆的业务接口作为参数)        Class<?> cl = getProxyClass0(loader, intfs); try { // 获取cl对象的构造方法 final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) {                // BEGIN android-changed: Excluded AccessController.doPrivileged call. cons.setAccessible(true); // END Android-removed: Excluded AccessController.doPrivileged call. }            // 通过构造方法,以反射的方式返回一个动态代理对象 return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }

这是个静态方法,有三个参数:

ClassLoader:类加载器,用来加载claas文件,和加载被代理类的类加载器是一致的Class<?>[]:共同的业务接口InvocationHandler:业务处理回调,被代理类真实业务处理的地方

    我们可以看到,第二个参数是数组!也就是说可以同时实现多个业务接口,并在回调中处理多个业务接口的业务!如果你看Retrofit的源码,你会发现Retrofit就是这么做的。newProxyInstance方法的返回值是一个Object,可以强转成一个接口对象。

    下面开始实践。直接上代码:

 private void commitNeedsToCompany(){ Log.d("fxp", "客户提交需求...");
Log.d("fxp", "指定项目经理Nami...");        // 动态生成代理对象,并强转为业务接口对象 IDoProject nami = (IDoProject) Proxy.newProxyInstance(zoro.getClass().getClassLoader(), new Class[]{IDoProject.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 整理需求 Log.d("fxp", "项目经理Nami整理需求...");
// 评估需求是否合理 boolean isReasonable = (new Random()).nextBoolean(); Log.d("fxp", "项目经理Nami评估需求是否合理...");
if (isReasonable){ // 需求合理-提交给程序员 Log.d("fxp", "需求评估结果为合理,项目经理Nami将需求提交给程序员Zoro...");
zoro.work(); } else { // 需求不合理-驳回 Log.d("fxp", "需求评估结果为不合理,项目经理Nami不将需求提交给程序员Zoro"); } return null; } }); nami.work(); }

运行结果:

23种设计模式探索之代理模式

    

    JDK动态代理是典型的面向接口编程,依赖于抽象而不依赖于具体,遵循依赖倒转原则。

    JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,可以使用CGLIB实现。至于CGLIB代理,在这里就不做叙述了。感兴趣的朋友可以自行搜索相关资料。


    如果根据代理模式的应用场景分类,代理模式又可以分为以下几类。

1,远程代理

2,虚拟代理

    如果需要创建一个消耗较大的对象,先创建一个消耗较小的对象来表示,真实对象只在需要时才被真实创建。比如Android中的AMS功能非常强大,但非常消耗资源,所以通过AMSProxy给App提供Activity管理部分的功能。

3,安全代理

    用来控制对真实对象的访问权限。


静态代理 VS 动态代理

静态代理

优点:

    1,业务类只需要关注业务逻辑本身;

缺点:

    1,可能需要为同一业务接口创建多个代理类,重复编码;

    2,业务接口一旦发生改变,所有代理类和被代理类都需要修改;

    3,一种代理类只服务于一种业务接口;(比如在上面静态代理示例中,ProjectManagerLuffy只代理ProgrammerZoro的IDoProject业务。如果还需要代理其他比如IPlayGames业务,就需要重新创建代理类了。)


JDK动态代理

优点:

    1,动态创建代理对象;

    2,可以同时代理多个业务接口;

    3,可以在InvocationHandler.invoke()中灵活处理接口方法;

缺点:

    1,只能代理实现了业务接口的被代理类;

    2,性能有损耗;


代理模式 VS 装饰模式

    看完上面静态代理的实践示例,可能有的同学就有疑问了,这不是装饰模式吗?有这个疑问是很好的,代理模式和装饰模式确实很容易混淆。

    不同设计模式之间区别,除了代码层面的实现方式,还有设计的目的。

    代理模式和装饰模式都属于结构型模式,在代码实现上确实没什么区别,但他们的目的是不一样的。在代理模式中,代理类对被代理类是有控制权的,可以决定被代理类做不做什么事情。在装饰模式中,装饰类对被装饰类是没有控制权的,装饰类通常只对其进行装饰,增强被装饰类的功能。

    还是以项目经理-程序员为例。如果项目经理过滤项目需求,决定哪些需求需要开发,哪些需求不需要开发,那么这就是代理模式了。如果项目经理对需求不做任何过滤,都交给程序员,提高程序员开发设备配置,或者提供程序员鼓励师,那么这就是装饰模式了。


    今天的分享就到此为止了,欢迎各位大佬批评指正。欢迎留言或私信交流讨论!



欢迎投稿欢迎投稿欢迎投稿!重要的事情说三遍!





以上是关于23种设计模式探索之代理模式的主要内容,如果未能解决你的问题,请参考以下文章

23种设计模式之代理模式

GoF 23 种设计模式之代理模式

23种设计模式之代理模式(Proxy)

23种设计模式之五(代理模式)

23种设计模式之代理模式

复习23种设计模式之代理模式