前言:上一篇文章写了《Head First设计模式》中代理模式的动态代理(详见:【设计模式】代理模式(动态代理))部分,本篇文章将介绍代理模式的远程代理和虚拟代理部分。由于这两个部分在书中是放在动态代理部分之前的,所以可以先阅读本篇文章。
一、定义代理模式
代理模式为另一个对象提供一个替身或者占位符以控制对这个对象的访问。
使用代理模式创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或者需要安全控制的对象。
代理模式之所以需要控制访问,是因为我们的客户不知道如何和远程对象沟通。从某些方面来看,远程代理控制访问,可以帮我们处理网络上的细节。如刚刚所说过的,代理模式有许多变体,这些变体几乎都和“控制访问”的做法有关。这几种代理控制的访问方式:
- 远程代理控制访问远程对象 (java远程方法调用)
- 虚拟代理控制访问创建开销大的资源
- 保护代理基于权限控制对资源的访问(详见:【设计模式】代理模式(动态代理))
类图如下:
从这张类图中,我们可以看出:
RealSubject和Proxy都实现了Subject接口。通过实现同一接口,Proxy在RealSubject出现的地方取代它。
RealSubject是真正做事的对象,它是被Proxy代理和控制访问的对象。Proxy持有RealSubject对象的引用。在一些例子中,Proxy还负责RealSubject对象的创建和销毁。客户和RealSubject的交互都必须通过Proxy。因为Proxy和RealSubject实现了相同的接口,所以任何用到RealSubject的地方,都可以用Proxy替代。Proxy也控制了对RealSubject的访问。
二、虚拟代理
现在已经了解了代理模式的定义,和一个特定的例子(远程代理),接下来看另一种代理:虚拟代理。让我们来看看远程代理和虚拟代理的比较:
远程代理
远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到运程执行,并且结果会通过网络返回给代理,再由代理转发给客户。
虚拟代理
虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到真正需要创建一个对象的时候才创建它。当对象在创建前和创建中时,有虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。
三、虚拟代理示例-CD Cover Viewer
我们将创建一个应用程序,用来展示CD唱片的封面。本程序使用java swing,通过创建一个Icon接口从网络上加载图片。由于网络的原因,下载可能需要一段时间,在等待图像加载的这段时间里,我们应该显示一些东西,同时我们不希望在这段时间里整个应用程序被挂起。一旦图像加载完毕,刚刚显示的东西应该消失,图像显现出来。
想要实现该功能,简单的方式就是利用虚拟代理。虚拟代理代替Icon,在图片未加载完成时显示“CD封面加载中,请稍后……”,一旦图片加载完成,代理就把显示的职责委托给Icon。
ImageProxy工作方式如下:
1. ImageProxy首先创建一个ImageIcon,然后开始从网络上加载图片。
2. 在加载工程中,ImageProxy显示”CD封面加载中,请稍后……“。
3. 当图像加载完毕后,ImageProxy把所有方法调用委托给真正的ImageIcon,这些方法包括 paintIcon()、getWidth()、getHeight()。
4.如果用户请求新的图像,则重新创建代理,重复上述过程。
代码如下:
ImageProxy.java类:
package com.pattern.proxy.virtual;
import java.awt.Component;
import java.awt.Graphics;
import java.net.URL;
import javax.swing.Icon;
import javax.swing.ImageIcon;
/**
* ImageProxy实现了Icon接口
*
* @date:2017年3月15日 下午10:53:51
*/
public class ImageProxy implements Icon {
/**
* imageIcon在加载后显现出真正的图像
*/
private ImageIcon imageIcon;
/**
* 图像的url,在构造方法中传入
*/
private URL imageURL;
private Thread retrievalThread;
private boolean retrieving = false;
public ImageProxy(URL imageURL) {
this.imageURL = imageURL;
}
@Override
public void paintIcon(final Component c, Graphics g, int x, int y) {
if(imageIcon != null) { // 如果imageIcon不为null,则进行绘制
imageIcon.paintIcon(c, g, x, y);
} else { // 否则,显示“加载中”
g.drawString("Loading CD cover, please wait...", x + 300, y + 190);
if(!retrieving) { // 如果还没有试着取出图像,则开始取出图像
retrieving = true;
// 不希望用户界面被挂起,开启线程去取出图像
retrievalThread = new Thread(new Runnable() {
@Override
public void run() {
try {
/*
* 在线程中实例化Icon对象,图像完成加载后会返回
* 当图像准备好后,通知swing进行重绘
* */
imageIcon = new ImageIcon(imageURL, "CD Cover");
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
/**
* imageIcon还完成加载前,返回默认值,
* 加载完成后,转给imageIcon处理
*/
@Override
public int getIconWidth() {
if(imageIcon != null) {
return imageIcon.getIconWidth();
} else {
return 800;
}
}
@Override
public int getIconHeight() {
if(imageIcon != null) {
return imageIcon.getIconHeight();
} else {
return 600;
}
}
}
ImageComponent.java类:
package com.pattern.proxy.virtual;
import java.awt.Graphics;
import javax.swing.Icon;
import javax.swing.JComponent;
public class ImageComponent extends JComponent {
private Icon icon;
public ImageComponent(Icon icon) {
this.icon = icon;
}
public void setIcon(Icon icon) {
this.icon = icon;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = icon.getIconWidth();
int h = icon.getIconHeight();
int x = (800 - w) / 2;
int y = (600 - h) / 2;
icon.paintIcon(this, g, x, y);
}
}
package com.pattern.proxy.virtual;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
public class ImageProxyTestDriver {
private ImageComponent imageComponent;
private JFrame frame = new JFrame("CD Cover Viewer");
private JMenuBar menuBar;
private JMenu menu;
private Hashtable<String, String> cds = new Hashtable<String, String>();
public static void main(String[] args) throws Exception {
ImageProxyTestDriver testDriver = new ImageProxyTestDriver();
}
public ImageProxyTestDriver() throws Exception {
cds.put("深夜的歌", "http://img1.doubanio.com/view/site/median/public/c083d9c5d4a2ba7.jpg");
cds.put("一如年少模样", "http://img1.doubanio.com/view/site/median/public/f788123243562e7.jpg");
cds.put("放肆的肆", "http://img1.doubanio.com/view/site/median/public/fc00cb95a9b74d9.jpg");
cds.put("歌选", "http://img3.doubanio.com/view/music_index_feature/mid/public/c49312.jpg");
URL initialURL = new URL(cds.get("深夜的歌"));
menuBar = new JMenuBar();
menu = new JMenu("Favorite CDs");
menuBar.add(menu);
frame.setJMenuBar(menuBar);
for(Enumeration<String> e = cds.keys(); e.hasMoreElements();) {
String name = e.nextElement();
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand());
imageComponent.setIcon(new ImageProxy(getCDUrl(e.getActionCommand())));
frame.repaint();
}
});
}
// 建立框架和菜单
Icon icon = new ImageProxy(initialURL);
imageComponent = new ImageComponent(icon);
frame.getContentPane().add(imageComponent);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setVisible(true);
}
private URL getCDUrl(String name) {
try {
return new URL(cds.get(name));
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
}
运行结果:
加载中
加载完成