Design Pattern: Proxy 模式(二)
 

2009-09-17 来源:riabook.cn

 

延续 Proxy模式(一) 的议题,来看看实现代理的两种方式:Static Proxy与Dynamic Proxy。严格来说这是属于模式的实现方式,不过藉由实例可以更了解Proxy模式的应用。

先来看个例子,这个例子是记录(log)动作,程式中很常需要为某些动作或事件作下记录,以便在事后检视或是作为除错时的资讯,一个最简单的例子如下:

  • HelloSpeaker.java
    import java.util.logging.*; 
    
    public class HelloSpeaker { 
        private Logger logger = 
                   Logger.getLogger(this.getClass().getName()); 
    
        public void hello(String name) { 
            logger.log(Level.INFO, "hello method starts....");
    
            System.out.println("Hello, " + name); 
    
            logger.log(Level.INFO, "hello method ends...."); 
        } 
    } 

HelloSpeaker在执行hello()方法时,您希望能记录该方法已经执行及结束,最简单的作法就是如上在执行的前后加上记录动作,然而 Logger介入了HelloSpeaker中,记录这个动作并不属于HelloSpeaker,这使得HelloSpeaker增加了非业务上需要的逻辑在当中。

想想如果程式中这种记录的动作到处都有需求,上面这种写法势必造成必须复制记录动作的程式码,使得维护记录动作的困难度加大。如果不只有记录动作,有一些非物件本身职责的相关动作也混入了物件之中(例如权限检查、事务管理等等),会使得物件的负担更形加重,甚至混淆了物件的职责,物件本身的职责所占的程式码,或许远小于这些与物件职责不相关动作的程式码。

怎么办,用下面的方法或许好一些,先定义一个介面,然后实作该介面:

  • IHello.java
    public interface IHello { 
        public void hello(String name); 
    } 
  • HelloSpeaker.java
    public class HelloSpeaker implements IHello { 
        public void hello(String name) { 
            System.out.println("Hello, " + name); 
        } 
    } 
    

接下来实作一个代理物件HelloProxy:

  • HelloProxy.java
    import java.util.logging.*; 
    
    public class HelloProxy implements IHello { 
        private Logger logger = 
                  Logger.getLogger(this.getClass().getName()); 
        private IHello helloObject; 
    
        public HelloProxy(IHello helloObject) { 
            this.helloObject = helloObject; 
        } 
    
        public void hello(String name) { 
            logger.log(Level.INFO, "hello method starts...."); 
    
            helloObject.hello(name); 
    
            logger.log(Level.INFO, "hello method ends...."); 
        } 
    }
    

执行时可以如此:

IHello helloProxy = new HelloProxy(new HelloSpeaker());
helloProxy.hello("Justin");

代理物件HelloProxy将代理真正的HelloSpeaker来执行hello(),并在其前后加上记录的动作,这使得 HelloSpeaker在撰写时不必介入记录动作,HelloSpeaker可以专心于它的职责。

这是Static Proxy的基本范例,然而如您所看到的,代理物件的一个介面只服务于一种类型的物件,而且如果要代理的方法很多,势必要为每个方法进行代理, Static Proxy在程式规模稍大时就必定无法胜任。

Java在JDK 1.3之后加入协助开发Dynamic Proxy功能的类别,我们不必为特定物件与方法撰写特定的代理,使用Dynamic Proxy,可以使得一个handler服务于各个物件,首先,一个handler必须实现 java.lang.reflect.InvocationHandler:

  • LogHandler.java
    import java.util.logging.*; 
    import java.lang.reflect.*; 
    
    public class LogHandler implements InvocationHandler { 
        private Logger logger = 
                   Logger.getLogger(this.getClass().getName()); 
        private Object delegate; 
    
        public Object bind(Object delegate) { 
            this.delegate = delegate; 
            return Proxy.newProxyInstance(
                     delegate.getClass().getClassLoader(), 
                     delegate.getClass().getInterfaces(), 
                     this); 
        }
     
        public Object invoke(Object proxy, 
                             Method method, 
                             Object[] args) throws Throwable {
            Object result = null; 
            try { 
                logger.log(Level.INFO, 
                             "method starts..." + method); 
                result = method.invoke(delegate, args); 
                logger.log(Level.INFO, 
                             "method ends..." + method); 
            } catch (Exception e){ 
                logger.log(Level.INFO, e.toString()); 
            } 
            return result; 
        } 
    } 
    

InvocationHandler的invoke()方法会传入被代理物件的方法名称与执行参数实际上要执行的方法交由method.invoke (),并在其前后加上记录动作,method.invoke()传回的物件是实际方法执行过后的回传结果。

Dynamic Proxy必须宣告介面,实作该介面,例如:

  • IHello.java
    public interface IHello { 
        public void hello(String name); 
    } 
    
  • HelloSpeaker.java
    public class HelloSpeaker implements IHello { 
        public void hello(String name) { 
            System.out.println("Hello, " + name); 
        } 
    } 
    

java.lang.reflect.Proxy的newProxyInstance()依要代理的物件、介面与handler产生一个代理物件,我们可以使用下面的方法来执行程式:

LogHandler logHandler = new LogHandler();
IHello helloProxy = (IHello) logHandler.bind(
new HelloSpeaker());
helloProxy.hello("Justin");

LogHandler不在服务于特定物件与介面,而HelloSpeaker也不用插入任何有关于记录的动作,它不用意识到记录动作的存在。


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织