zoukankan      html  css  js  c++  java
  • 代理模式

    本文讲述了代理模式,包括了普通代理、强制代理和动态代理。

    原文链接:

    http://tianweili.github.io/blog/2015/03/13/proxy-pattern/

    介绍

    代理模式属于结构性模式,使用频率很高。

    定义:Provide a surrogate or placeholder for another object to control access to it.为其他对象提供一种代理以控制这个对象的访问。

    装饰模式、状态模式、策略模式、访问者模式本质上都是在更特殊的场合采用了代理模式。

    UML类图

    {% img /design-pattern/proxy-pattern-uml.png %}

    从以上UML类图中可以看出代理模式主要有三种角色:

    抽象主题角色

    可以是接口或抽象类,是某类通用业务的定义。

    具体主题角色

    作为被代理对象,是具体业务的真实执行者。

    代理主题角色

    是具体主题对象的代理,负责对具体对象的应用,把所有抽象主题定义的方法委托给具体主题对象来实现,它用来在具体主题对象业务处理的前后做一些处理工作。

    代码示例

    /**
     * Abstract subject class.
     */
    public interface Subject {
        public void someMethod();
    }
    /**
     * Real subject class.
     */
    public class RealSubject implements Subject {
    
        private String name;
    
        public RealSubject(String name) {
            this.name = name;
        }
    
        @Override
        public void someMethod() {
            System.out.println(name + "'s someMehtod.");
        }
    }
    /**
     * Proxy class.
     */
    public class ProxySubject implements Subject {
    
        private Subject subject;
    
        public ProxySubject(Subject subject) {
            this.subject = subject;
        }
    
        @Override
        public void someMethod() {
            this.before();
            subject.someMethod();
            this.after();
        }
    
        /**
         * preprocessing
         */
        private void before() {
            // do something...
        }
        
        /**
         * follow-up processing
         */
        private void after() {
            // do something...
        }
    }
    

    从以上代码可以看出ProxySubject对象还有before和after方法,可以在RealSubject对象的someMethod业务方法前后做一些预处理和善后处理工作。

    一个代理类可以代理多个被代理对象,只要是实现同一个接口。当然也可以一个被代理对象就有一个代理类,不过一般是一个接口有一个代理类就够了,在应用时具体是代理哪一个被代理对象,这是由场景类也就是高层模块定义的,根据构造方法的传入哪一个被代理对象参数来决定代理哪一个对象。

    构造方法:

    public ProxySubject(Subject subject) {
        this.subject = subject;
    }
    

    想代理哪个对象就要传入生成这个对象的实例。

    优点

    各个角色职责清晰,比如被代理对象只需要实现属于自己具体的业务逻辑就行了,不用去关心非本职责的业务处理。其他的一些处理业务可以交给代理类来处理。这样做的好处是编程简洁清晰,业务分明。

    扩展性好,具体实现对象的业务发生了变化,只需要修改自身业务处理逻辑,或者增加删减一个实现业务接口的对象,不会影响代理业务。

    代理模式可以提供非常好的访问控制,由代理类来控制被代理对象,可以做一些预处理消息,过滤消息,消息转发和善后处理工作等等。

    普通代理和强制代理

    普通代理和强制代理是代理模式的两种不同结构,是根据调用者能够访问到代理对象还是具体对象来区分的。就好比网络上的代理服务器设置分为普通代理和透明代理。普通代理需要用户手动设置代理服务器的IP地址,用户必须知道代理的存在。透明代理就是用户不需要设置代理服务器地址,就可以直接访问,不用知道代理的存在。

    普通代理

    普通代理是用户只能访问代理角色,而不能访问真实角色。

    只需要对上面代码稍作改动即可实现普通代理的效果,代码如下:

    /**
     * Abstract subject class.
     */
    public interface Subject {
        public void someMethod();
    }
    /**
     * Real subject class.
     */
    public class RealSubject implements Subject {
    
        private String name;
    
        /**
         * Don't allow client call this.
         */
        public RealSubject(Subject subject, String name) throws Exception {
            if(subject == null) {
                throw new Exception("Don't allow realSubject created!");
            } else {
                this.name = name;
            }
        }
    
        @Override
        public void someMethod() {
            System.out.println(name + "'s someMehtod.");
        }
    }
    /**
     * Proxy class.
     */
    public class ProxySubject implements Subject {
    
        private Subject subject;
    
        /**
         * Client call proxy subject.
         */
        public ProxySubject(String name) {
            try {
                this.subject = new RealSubject(this, name);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void someMethod() {
            this.before();
            subject.someMethod();
            this.after();
        }
    
        /**
         * preprocessing
         */
        private void before() {
            // do something...
            System.out.println("before ...");
        }
        
        /**
         * follow-up processing
         */
        private void after() {
            // do something...
            System.out.println("after ...");
        }
    }
    public class Client {
        public static void main(String[] args) {
            Subject subject = new ProxySubject("ZhangSan");
            subject.someMethod();
        }
    }
    

    运行程序结果如下:

    before ...
    ZhangSan's someMehtod.
    after ...
    

    在上面RealSubject的构造方法中是通过传入参数subject来限制用户不能实例化自己,当然也可以通过别的一些限制条件,比如类名必须有Proxy等等。

    强制代理

    普通代理是通过代理角色找到真是角色,而强制代理是强制只能通过真实角色查找代理角色来访问,想直接通过实例化代理角色或真实角色都不能访问。

    UML类图如下:

    {% img /design-pattern/proxy-pattern-uml-2.png %}

    从以上UML类图可以看出Subject接口中添加了个获取代理的接口方法。

    代码清单:

    /**
     * Abstract subject class.
     */
    public interface Subject {
        public void someMethod();
        public Subject getProxy();
    }
    /**
     * Real subject class.
     */
    public class RealSubject implements Subject {
    
        private String name;
        private Subject proxy;
    
        public RealSubject(String name) {
            this.name = name;
        }
    
        @Override
        public void someMethod() {
            if (this.isProxy()) {
                System.out.println(name + "'s someMehtod.");
            } else {
                System.out.println("Only visit proxy class is allowed!");
            }
        }
    
        @Override
        public Subject getProxy() {
            // appoint proxy class.
            this.proxy = new ProxySubject(this);
            return proxy;
        }
    
        private boolean isProxy() {
            if (this.proxy == null) {
                return false;
            } else {
                return true;
            }
        }
    }
    /**
     * Proxy class.
     */
    public class ProxySubject implements Subject {
    
        private Subject subject;
    
        public ProxySubject(Subject subject) {
            this.subject = subject;
        }
    
        @Override
        public void someMethod() {
            subject.someMethod();
        }
    
        @Override
        public Subject getProxy() {
            return this;
        }
    }
    

    当客户端想通过真实角色来访问时,客户端代码如下:

    public class Client {
        public static void main(String[] args) {
            Subject realSubject = new RealSubject("ZhangSan");
            realSubject.someMethod();
        }
    }
    

    执行结果:

    Only visit proxy class is allowed!
    

    访问被拒绝,因为它是通过真实角色来直接访问的,而不是通过真实角色来获取代理角色来访问。

    当客户端想通过代理角色来访问时,客户端代码如下:

    public class Client {
        public static void main(String[] args) {
            Subject realSubject = new RealSubject("ZhangSan");
            Subject proxy = new ProxySubject(realSubject);
            proxy.someMethod();
        }
    }
    

    执行结果:

    Only visit proxy class is allowed!
    

    访问同样被拒绝,因为它是通过代理角色来直接访问的,而不是通过真实角色来获取代理角色来访问。

    只有强制客户端通过真实角色来获取代理对象,才能访问。客户端代码如下:

    public class Client {
        public static void main(String[] args) {
            Subject realSubject = new RealSubject("ZhangSan");
            Subject proxy = realSubject.getProxy();
            proxy.someMethod();
        }
    }
    

    执行结果:

    ZhangSan's someMehtod.
    

    通过真实角色来获取代理对象访问成功。

    动态代理

    这一节之前所讲的代理其实都是静态代理,它有一个特点就是要在实现阶段就要指定代理类以及被代理者,很不灵活。而动态代理就是在实现阶段不用管代理具体对象,而在运行阶段指定代理哪个对象即可生产代理对象。

    基本的UML类图如下所示:

    {% img /design-pattern/proxy-pattern-uml-3.png %}

    从类图中可以看出,具体的业务逻辑和代理逻辑是两条线,两者之间没有必然的耦合关系。

    InvocationHandler是JDK提供的接口,用来对被代理类的方法进行代理。

    注意:被代理者必须实现一个接口,否则动态代理无法生成代理对象。

    动态代理是根据被代理者的接口生成所有的方法。通过InvocationHandler接口,所有被代理的方法都由InvocationHandler来接管实际的处理逻辑。

    代码清单:

    /**
     * Abstract subject class.
     */
    public interface Subject {
        public void someMethod();
    }
    /**
     * Real subject class.
     */
    public class RealSubject implements Subject {
    
        private String name;
    
        public RealSubject(String name) {
            this.name = name;
        }
    
        @Override
        public void someMethod() {
            System.out.println(name + "'s someMehtod.");
        }
    }
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class MyInvocationHandler implements InvocationHandler {
    
        private Object obj;
    
        public MyInvocationHandler(Object obj) {
            this.obj = obj;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before...");
            Object result = method.invoke(obj, args);
            System.out.println("after...");
            return result;
        }
    }
    

    invoke方法是接口InvocationHandler中定义必须实现的,它用来完成对真实方法的调用。

    客户端调用代码:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class Client {
        public static void main(String[] args) {
            Subject subject = new RealSubject("ZhangSan");
            
            ClassLoader loader = subject.getClass().getClassLoader();
            Class[] interfaces = subject.getClass().getInterfaces();
            InvocationHandler handler = new MyInvocationHandler(subject);
            
            Subject proxy = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
            proxy.someMethod();
        }
    }
    

    执行结果:

    before...
    ZhangSan's someMehtod.
    after...
    

    从结果中可以看出我们已经达到了代理RealSubject对象的目的。

    看了上面的客户端调用代码,我们可以优化一下,将Proxy封装起来,使得调用更简便一些。增加动态代理封装类:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class DynamicProxy {
        
        public static Object newProxyInstance(Object object) {
            
            ClassLoader loader = object.getClass().getClassLoader();
            Class[] interfaces = object.getClass().getInterfaces();
            InvocationHandler handler = new MyInvocationHandler(object);
            
            return Proxy.newProxyInstance(loader, interfaces, handler);
        }
    }
    

    客户端调用:

    public class Client {
        public static void main(String[] args) {
            Subject subject = new RealSubject("ZhangSan");
            Subject proxy = (Subject) DynamicProxy.newProxyInstance(subject);
            proxy.someMethod();
        }
    }
    

    动态代理优点

    动态代理拥有以上静态代理所有优点,除此之外还有动态代理的代理对象是在需要的时候动态生成的。

    在业务逻辑开发时可以不用管代理业务逻辑,这两条线不会耦合。比如在做具体的业务逻辑设计和实现时不用考虑日志、事务、权限等逻辑处理,这些可以通过动态代理来搞定。

    Struts2的Form映射和Spring的AOP(Aspect Oriented Programming)就是动态代理的典型应用。

    作者:李天炜

    原文链接:http://tianweili.github.io/blog/2015/03/13/proxy-pattern/

    转载请注明作者和文章出处,谢谢。

  • 相关阅读:
    Opencv CamShift+Kalman目标跟踪
    Opencv混合高斯模型前景分离
    TTabControl、TMemo组件(制作一个简单的多文本编辑框)
    ShowMessage和MessageDlg消息对话框(VCL)
    TPageControl组件
    TImageList 和 TlistView 组件(C++Builder)
    C# 动态链接库的创建
    线程的并发与并行
    OpenCv haar+SVM训练的xml检测人头位置
    C++Builder组件
  • 原文地址:https://www.cnblogs.com/hellojava/p/4336127.html
Copyright © 2011-2022 走看看