Java设计模式之代理模式篇(3)  图5 ImageIcon代理的时序图 图5中的时序图具有鲜明的代理模式的特点:代理控制对真实对象的存取操作。由于这个原因,在代理类中通常要生成真实对象的实例,就像在ImageIconProxy类中那样。这也是代理模式和修饰模式之间的区别,修饰者很少生成真实对象的实例。
JDK中对代理模式的支持
代理模式是Java中最重要的模式之一,它通过另一种方式来扩展类的功能(通常在程序中是通过继承来扩展类的功能),这种方式又被称为对象合成(Object Composition)。在这种方式中,包含对象(在代理模式中是代理)将方法调用传递给被包含对象(在代理模式中是真实对象)。
对象合成和对象继承相比有他的优点,这是因为在对象合成中,包含对象只能够通过接口对它所包含的对象进行操作,这样包含对象和被包含对象之间是弱连接的关系。而在继承关系中,父类和子类之间的关系相当紧密,父类中的方法和属性可以被子类看到。由于这种关系,继承也被称为白盒重用(whit-box reuse)。而在对象合成中,包含对象中和被包含对象不能够看到对方的方法和属性,因此也被称为黑盒重用(black-box reuse)。很显然,黑盒重用比白盒重用更好,它的弱连接特性可以使最终的软件系统具有良好的可扩展性和灵活性。
由于代理模式的重要性,J2SE和以后的版本直接支持该模式。这种支持体现在java.lang.reflect包中的Proxy,Method和InvocationHandler类中。例五中演示了一个简单的例子:
例5 JDK中的代理模式的应用
import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface AnInterface { public void doSomething(); } class AClass implements AnInterface { public void doSomething() { System.out.println("Inside Method AClass.doSomething()"); } } public class Test { public static void main(String args[]) { AnInterface realSubject = new AClass(); AnInterface proxy = (AnInterface)Proxy.newProxyInstance( realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), new SimpleInvocationHandler(realSubject)); passMeAProxy(proxy); } private static void passMeAProxy(AnInterface anInterface) { anInterface.doSomething(); } } class SimpleInvocationHandler implements InvocationHandler { public SimpleInvocationHandler(Object realSubject) { this.realSubject = realSubject; } public Object invoke(Object proxy, Method m, Object[] args){ Object result = null; System.out.println("Before Calling " + m.getName()); try { result = m.invoke(realSubject, args); } catch(Exception ex) { System.exit(1); } System.out.println("After Calling " + m.getName()); return result; } private Object realSubject = null; } |
在例5的代码中,静态方法Proxy.newProxyInstance()创建了一个真实对象的代理对象。真实对象必须实现一个以上的接口,并且对代理对象的引用必须能够传递给那些将上述接口作为参数的方法。在main()方法中将代理对象传递给需要AnInterface接口类型参数的方法来表明代理能够正常工作。上面的代码中代理只实现了一个接口。
Proxy.newProxyInstance()方法需要三个参数:加载真实对象的类加载器,真实对象实现的接口的列表和对调用处理者(Invocation Handler)的引用。每当程序中调用代理类中的方法时,代理对象会使用调用处理者中的invoke()方法。代理对象将自己的引用、调用的方法以及方法的参数列表传递给invoke()方法。在上面的代码中SimpleInvocationHandler.invoke()方法调用了真实对象中的方法。
例6中的代码使用JDK中提供的代理类实现了ImageIcon例子:
例6 使用JDK中的代理类实现ImageIcon例子
import java.lang.reflect.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; // 该类测试一个代理。该代理将图形的显示延迟到第一次刷新图片的时候 public class VirtualProxyTest extends JFrame { private static String IMAGE_NAME = "hands.jpg"; private static final int IMAGE_WIDTH = 256, IMAGE_HEIGHT = 256, SPACING = 5, FRAME_X = 150, FRAME_Y = 200, FRAME_WIDTH = 530, FRAME_HEIGHT = 286; private ImageIcon imageIcon = null; private Icon imageIconProxy = null; static public void main(String args[]) { VirtualProxyTest app = new VirtualProxyTest(); app.show(); } public VirtualProxyTest() { super("ImageIcon测试"); // 创建一个ImageIcon对象的和ImageIcon代理 imageIcon = new ImageIcon(IMAGE_NAME); imageIconProxy = (Icon)Proxy.newProxyInstance( imageIcon.getClass().getClassLoader(), imageIcon.getClass().getInterfaces(), new ImageIconInvocationHandler(IMAGE_NAME, IMAGE_WIDTH, IMAGE_HEIGHT)); // 设定窗口大小和关闭窗口的操作 // close operation. setBounds(FRAME_X, FRAME_Y, FRAME_WIDTH, FRAME_HEIGHT); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void paint(Graphics g) { super.paint(g); Insets insets = getInsets(); imageIcon.paintIcon(this, g, insets.left, insets.top); imageIconProxy.paintIcon(this, g, insets.left + IMAGE_WIDTH + SPACING, // 宽 insets.top); // 高 } } // ImageIconInvocationHandler.java import java.lang.reflect.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; // ImageIconInvocationHandler类是一个ImageIcon的代理。它将图形的 // 显示延迟到图形第一次被绘制的时候。当图形还没有被绘制以前, // 代理在界面上显示"加载图片…"的信息 class ImageIconInvocationHandler implements InvocationHandler { private static final String PAINT_ICON_METHOD="paintIcon", GET_WIDTH_METHOD ="getIconWidth", GET_HEIGHT_METHOD="getIconHeight"; private ImageIcon realIcon = null; private boolean isIconCreated = false; private String imageName = null; private int width, height; public ImageIconInvocationHandler(String imageName, int width, int height) { this.width = width; this.height = height; this.imageName = imageName; } public Object invoke(Object proxy, Method m, Object[] args) { Object result = null; if(PAINT_ICON_METHOD.equals(m.getName())) { final Component c = (Component)args[0]; final Graphics g = (Graphics)args[1]; final int x = ((Integer)args[2]).intValue(), y = ((Integer)args[3]).intValue(); if(isIconCreated) { realIcon.paintIcon(c, g, x, y); } else { g.drawRect(x, y, width-1, height-1); g.drawString("Loading image...", x+20, y+20); // 在单独的线程中创建图形对象 SwingUtilities.invokeLater(new Runnable() { public void run() { try { Thread.currentThread().sleep(2000); realIcon = new ImageIcon(imageName); isIconCreated = true; } catch(InterruptedException ex) { ex.printStackTrace(); } c.repaint(); } }); } } else if(GET_WIDTH_METHOD.equals(m.getName())) { return isIconCreated ? new Integer(height) : new Integer(realIcon.getIconHeight()); } else if(GET_HEIGHT_METHOD.equals(m.getName())) { return isIconCreated ? new Integer(width) : new Integer(realIcon.getIconWidth()); } return null; } } |
如果对例6将和例3作一个比较,也许你会发觉JDK中提供的代理类用起来并不是很方便。在这种情况下,使用例三中的实现方式更合理一些。但是当代理类实现了很多方法,并且将这些方法的实现委托给真实对象时,JDK中的代理类能够简化编码的工作。
代理模式的应用
当开发人员需要对一个对象的存取操作进行控制的时候就需要使用代理模式。通常在下列情况中会经常使用到代理模式:远程代理、虚拟代理和保护代理。远程代理用于控制对远程对象的存取,例如在本文开头提到的Web服务代理。虚拟代理用于控制那些比较耗费资源的对象实例的生成,ImageIcon就是一个很好的例子。保护代理用于控制用户对一些功能的使用。在保护代理模式中,只有特定的用户才能使用特定的功能。
那些实现了若干个接口的类并且只有为数不多的方法的类很适合于作为代理的真实对象类,根据这样的类实现的代理简明而且易于维护。例如,Icon接口只定义了三个方法,因此很容易实现相应的代理。但是如果使用的是继承了JFrame的类作为真实对象的话,就需要实现上百个方法将对不同方法的调用传递给真实对象。不过JDK提供的代理类可以使这项繁杂的工作变得相对简单。(全文完)
|