软件设计模式学习(十六)代理模式

Posted yee-q

tags:

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


当直接访问某些对象存在问题时,可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。


模式动机

某些情况下,一个客户不想或不能直接引用一个对象,此时可以通过一个称之为代理的第三者实现间接引用。代理对象在客户端和目标对象之间起到中介作用,并且可以通过代理对象去掉客户不能看到的内容和添加客户需要的额外服务。


模式定义

给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做 Proxy 或 Surrogate,它是一种对象结构模式。


模式结构

技术图片

  1. Subject(抽象主题角色)

    声明了真实主题和代理主题的公共接口,这样一来在任何使用真实主题的地方都可以使用代理主题。客户端针对抽象主题角色编程。

  2. Proxy(代理主题角色)

    代理主题角色内部包含对真实主题的引用,从而可以在任何时候操作真实主题角色。

    代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候替代真实主体。

    代理主题角色还可以控制对真实主题的使用,负责在需要时创建和删除真实主题对象,并对真实主题对象的使用加以约束。

    代理角色通常在客户端调用所引用的真实主题操作之前或之后执行其他操作,而不仅仅只是单纯调用真实主题对象中的操作。

  3. RealSubject(真实主题角色)

    真实主题角色定义了代理角色所代表的真实对象,真实主题角色中实现真实的业务,客户端通过代理主题角色间接调用真实主题角色中定义的方法。


模式实例与解析

在一个论坛已注册用户和游客权限不同,已注册用户拥有发帖、修改注册信息、修改自己帖子等功能;而游客只能看到别人发的贴子,没有其他权限。本实例中我们使用代理模式中的保护代理,该代理用于控制对一个对象的访问,可以给不同用户提供不同级别的使用权限。

技术图片

  1. 抽象主题角色 AbstractPermission(抽象权限类)

    AbstractPermission 作为抽象权限类,充当抽象主题角色,在其中声明了真实主题角色所提供的业务方法,它是真实主题角色和代理主题角色的公共接口

    public interface AbstractPermission {
    
        public void modifyUserInfo();
    
        public void viewNote();
    
        public void publishNote();
    
        public void modifyNote();
    
        public void setLevel(int level);
    }
    
  2. 真实主题角色 RealPermission(真实权限类)

    RealPermission 是真实主题角色,它实现了在抽象主题角色中定义的方法,由于种种原因客户端无法访问其中内容。

    public class RealPermission implements AbstractPermission {
    
        @Override
        public void modifyUserInfo() {
            System.out.println("修改用户信息");
        }
    
        @Override
        public void viewNote() { }
    
        @Override
        public void publishNote() {
            System.out.println("发布新帖");
        }
    
        @Override
        public void modifyNote() {
            System.out.println("修改发帖内容");
        }
    
        @Override
        public void setLevel(int level) { }
    }
    
  3. 代理主题角色 PermissionProxy(权限代理类)

    PermissionProxy 是代理主题角色,它也实现了抽象主题角色接口,同时在 PermissionProxy 中定义了一个 RealPermission 对象,用于调用 RealPermission 中定义的真实业务方法。通过引入 PermissionProxy 类来对系统的使用权限进行控制,这就是保护代理的用途。

    public class PermissionProxy implements AbstractPermission {
    
        private RealPermission permission = new RealPermission();
        private int level = 0;
    
        @Override
        public void modifyUserInfo() {
            if (0 == level) {
                System.out.println("对不起,你没有该权限");
            } else if (1 == level) {
                permission.modifyUserInfo();
            }
        }
    
        @Override
        public void viewNote() {
            System.out.println("查看帖子");
        }
    
        @Override
        public void publishNote() {
            if (0 == level) {
                System.out.println("对不起,你没有该权限");
            } else if (1 == level) {
                permission.publishNote();
            }
        }
    
        @Override
        public void modifyNote() {
            if (0 == level) {
                System.out.println("对不起,你没有该权限");
            } else if (1 == level) {
                permission.modifyNote();
            }
        }
    
        @Override
        public void setLevel(int level) {
            this.level = level;
        }
    }
    
  4. 客户端测试类 Client

    public class Client {
    
        public static void main(String[] args) {
    
            AbstractPermission permission = new PermissionProxy();
    
            permission.modifyUserInfo();
            permission.viewNote();
            permission.publishNote();
            permission.modifyNote();
    
            System.out.println("-------------------------");
    
            permission.setLevel(1);
            permission.modifyUserInfo();
            permission.viewNote();
            permission.publishNote();
            permission.modifyNote();
        }
    }
    
  5. 运行结果
    技术图片

    代理类可以对用户访问权限进行控制,因此有些用户无权调用真实业务类的某些方法,当用户权限改变时,则可以访问这些方法。如果需要增加并使用新的代理类,首先将新增代理类作为抽象主题角色的子类,实现在抽象主题中声明的方法。


模式优缺点

代理模式优点如下:

  1. 代理模式能协调调用者和被调用者,在一定程度上降低了系统的耦合度
  2. 远程代理使客户端能访问在远程机器上的对象
  3. 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高速度
  4. 保护代理可以控制对真实对象的使用权限

代理模式缺点如下:

  1. 由于在客户端和真实主题之间增加了代理对象,因此可能会造成请求的处理速度变慢。
  2. 有些代理模式的实现十分复杂。

模式使用环境

根据代理模式的使用目的,常见的代理模式有以下几个类型。

  1. 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机,也可以是另一台主机。
  2. 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,可以先创建一个消耗较小的对象来表示,真实对象只在需要时才会被真正创建。
  3. Copy-on-Write代理:它是虚拟代理的一种,把复杂操作延迟到只在客户端真正需要时才执行。
  4. 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  5. 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户可以共享这些结果。
  6. 防火墙(FireWall)代理:保护目标不让恶意用户接近
  7. 同步化(Synchronization)代理:使几个用户能同时使用一个对象而没有冲突
  8. 智能(Smart Reference)引用:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来

静态代理

所谓静态代理,就是由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的 .class 文件就已经生成。简单来说,上述的实例就属于静态代理,PermissionProxy 代理类是我们定义好的,在程序运行之前就已经编译完成。


动态代理

传统的代理模式中,客户端通过 ProxySubject 调用 RealSubject 类的方法,同时还在代理类中封装了其他方法,可以处理一些其他问题。如果按照这种方法使用代理模式,那么真实主题角色必须是是事先已经存在的,并将其作为代理对象的内部成员属性。如果一个真实主题角色必须对应一个代理主题角色,这将导致系统中的类的个数急剧增加,因此需要想办法减少系统中类的个数。

Java 自带的基于接口的动态代理(即只能实现接口的代理)能在运行时根据我们在 Java 代码中的指示动态生成的代理类,其实现相关类位于 java.lang.reflect 包,运行时动态地对某些东西作代理,主要涉及两个类

  1. InvocationHandler 接口

    InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
    Each proxy instance has an associated invocation handler.When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

    InvocationHandler 是由代理实例的调用处理程序实现的接口。每个代理实例都有一个关联的调用处理程序。InvocationHandler 接口中定义了 invoke 方法,当代理实例调用某个方法时,该方法的调用将被编码并调度到其调用处理程序的 invoke 方法处理。

    /**
     *	处理代理实例上的方法调用并返回结果
     *	proxy 表示动态代理类
     *	method 表示需要代理的方法
     *	args 表示代理方法的参数数组
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    
  2. Proxy 类

    /**
     *	该类提供了用于为接口创建代理实例的静态方法
     */
    public class Proxy implements java.io.Serializable {
        ...
         /**
          *	根据传入的接口类型返回一个动态创建的代理类实例
          *	loader 表示被代理类的类加载器
          * interfaces 表示被代理类实现的接口列表(与真实主题类的接口列表一致)
          *	h 表示所指派的调用处理程序类
          */
    	public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
            ...
    }
    

    下面通过一个简单实例来学习动态代理,现在有两个真实主题类分别是 RealSubjectA 和 RealSubjectB,它们对于抽象主题类中定义的抽象方法 request() 提供了不同的实现,在不增加新的代理类的情况下,使得客户端通过一个代理类来动态选择所代理的真实主题对象

    1. 抽象主题接口 AbstractSubject

      public interface AbstractSubject {
      
          public void request();
      
      }
      
    2. 真实主题类一 RealSubjectA

      public class RealSubjectA implements AbstractSubject {
      
          @Override
          public void request() {
              System.out.println("真实主题类A");
          }
      }
      
    3. 真实主题类二 RealSubjectB

      public class RealSubjectB implements AbstractSubject {
      
          @Override
          public void request() {
              System.out.println("真实主题类B");
          }
      }
      
    4. 动态代理类 DynamicProxy

      public class DynamicProxy implements InvocationHandler {
      
          private Object obj;
      
          public DynamicProxy() {}
      
          public DynamicProxy(Object obj) {
              this.obj = obj;
          }
      
          //  实现 invoke() 方法,调用在真实主题类中定义的方法
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      
              System.out.println("调用之前");
              //  利用反射调用方法,如果方法没有返回值则为 null
              Object result = method.invoke(obj, args);
              System.out.println("调用之后");
              return result;
          }
      }
      
    5. 客户端测试类 Client

      public class Client {
      
          public static void main(String[] args) {
      
              AbstractSubject subject = new RealSubjectA();
              InvocationHandler handler = new DynamicProxy(subject);
              AbstractSubject subjectProxy = (AbstractSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
                      subject.getClass().getInterfaces(), handler);
              subjectProxy.request();
      
              System.out.println("-----------------------------");
      
              subject = new RealSubjectB();
              handler = new DynamicProxy(subject);
              subjectProxy = (AbstractSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
                      subject.getClass().getInterfaces(), handler);
              subjectProxy.request();
          }
      }
      
    6. 运行结果
      技术图片





以上是关于软件设计模式学习(十六)代理模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式 - 学习笔记 - 代理模式ProxyPattern

设计模式 - 学习笔记 - 代理模式ProxyPattern

(十六)设计模式总结

(@WhiteTaken)设计模式学习——代理模式

设计模式 | 结构型设计模式对比(十六)

结构型设计模式对比 设计模式(十六)