前言:上一篇文章寫了《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;
}
}
運行結果:
加載中
加載完成