《Head.First設計模式》的學習筆記(15)--代理模式
意圖:
為另一個物件提供一個替身或佔位符得以訪問這個物件。
結構:
接著我們來看RMI遠端代理:
1.我們先在伺服器註冊好幾個糖果機,由於我們現在使用RMI,我們需要構造糖果機和狀態。
糖果機首先變成一個服務,我們為糖果機建立一個遠端介面,讓開介面提供了一組可以遠端呼叫的的方法。
public interface GumballMachineRemote extends Remote { public int getCount() throws RemoteException; public String getLocation() throws RemoteException; public State getState() throws RemoteException; }
接著我們繼承這個介面的糖果機
import java.rmi.*; import java.rmi.server.*; public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote { State soldOutState; State noQuarterState; State hasQuarterState; State soldState; State winnerState; State state = soldOutState; int count = 0; String location; public GumballMachine(String location, int numberGumballs) throws RemoteException { soldOutState = new SoldOutState(this); noQuarterState = new NoQuarterState(this); hasQuarterState = new HasQuarterState(this); soldState = new SoldState(this); winnerState = new WinnerState(this); this.count = numberGumballs; if (numberGumballs > 0) { state = noQuarterState; } this.location = location; } public void insertQuarter() { state.insertQuarter(); } public void ejectQuarter() { state.ejectQuarter(); } public void turnCrank() { state.turnCrank(); state.dispense(); } void setState(State state) { this.state = state; } void releaseBall() { System.out.println("A gumball comes rolling out the slot..."); if (count != 0) { count = count - 1; } } public void refill(int count) { this.count = count; state = noQuarterState; } public int getCount() { return count; } public State getState() { return state; } public String getLocation() { return location; } public State getSoldOutState() { return soldOutState; } public State getNoQuarterState() { return noQuarterState; } public State getHasQuarterState() { return hasQuarterState; } public State getSoldState() { return soldState; } public State getWinnerState() { return winnerState; } public String toString() { StringBuffer result = new StringBuffer(); result.append("\nMighty Gumball, Inc."); result.append("\nJava-enabled Standing Gumball Model #2004"); result.append("\nInventory: " + count + " gumball"); if (count != 1) { result.append("s"); } result.append("\n"); result.append("Machine is " + state + "\n"); return result.toString(); } }
其中State要進行傳送,所以我們要將其序列化:
import java.io.*;
public interface State extends Serializable {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
由於狀態有糖果機的引用我們不希望糖果機被傳送,我們只要將其非序列化,如下:
public class NoQuarterState implements State { transient GumballMachine gumballMachine; public NoQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } public void insertQuarter() { System.out.println("You inserted a quarter"); gumballMachine.setState(gumballMachine.getHasQuarterState()); } public void ejectQuarter() { System.out.println("You haven't inserted a quarter"); } public void turnCrank() { System.out.println("You turned, but there's no quarter"); } public void dispense() { System.out.println("You need to pay first"); } public String toString() { return "waiting for quarter"; } }
加上transient,就可以避免序列化。
最後我們將其註冊到RMI registry中:
package headfirst.proxy.gumball;
import java.rmi.*;
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachineRemote gumballMachine = null;
int count;
if (args.length < 2) {
System.out.println("GumballMachine <name> <inventory>");
System.exit(1);
}
try {
count = Integer.parseInt(args[1]);
gumballMachine =
new GumballMachine(args[0], count);
Naming.rebind("//" + args[0] + "/gumballmachine", gumballMachine);
} catch (Exception e) {
e.printStackTrace();
}
}
}
接著我們來看客戶端,也就是監控機:
import java.rmi.*;
public class GumballMonitor {
GumballMachineRemote machine;
public GumballMonitor(GumballMachineRemote machine) {
this.machine = machine;
}
public void report() {
try {
System.out.println("Gumball Machine: " + machine.getLocation());
System.out.println("Current inventory: " + machine.getCount() + " gumballs");
System.out.println("Current state: " + machine.getState());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
接著我們在呼叫客戶端的時候實現代理,然後用代理呼叫遠端服務端的方法,程式碼如下:
import java.rmi.*;
public class GumballMonitorTestDrive {
public static void main(String[] args) {
//這些是代表服務端啟用了3個這樣地址的RMI服務端
String[] location = {"rmi://santafe.mightygumball.com/gumballmachine",
"rmi://boulder.mightygumball.com/gumballmachine",
"rmi://seattle.mightygumball.com/gumballmachine"};
GumballMonitor[] monitor = new GumballMonitor[location.length];
for (int i=0;i < location.length; i++) {
try {
//這個是指建立遠端代理,而且只是呼叫介面避免將真正的糖果機裸露在外
//由於是迴圈,為每個地址建立了一個代理
GumballMachineRemote machine =
(GumballMachineRemote) Naming.lookup(location[i]);
monitor[i] = new GumballMonitor(machine);
System.out.println(monitor[i]);
} catch (Exception e) {
e.printStackTrace();
}
}
for(int i=0; i < monitor.length; i++) {
//report方法呼叫了代理的遠端方法,相當於呼叫服務端的方法
monitor[i].report();
}
}
}
上述就是一個遠端代理的,遠端代理是可以作為另一個JVM上物件的本地代表。
接著我們將介紹虛擬代理,虛擬代理作為建立開銷大的物件的代表。虛擬物件經常直到我們真正需要一個物件的時候才建立它。當物件在建立前和建立中,有虛擬物件來扮演物件的替身,物件建立後,代理就會將請求直接委託給物件。
例子:
我們經常會碰到JFrame載入一個大的網路圖片,這時候我們就可以先使用代理顯示正在載入圖片,等圖片真正載入好我們才“畫”上這個圖片,先看類圖:
Icon是使用Swing的Icon介面,在使用者介面上顯示圖片。
接著我們來實現ImageIcon繼承了Icon介面
package headfirst.proxy.virtualproxy;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class ImageProxy implements Icon {
ImageIcon imageIcon;
URL imageURL;
Thread retrievalThread;
boolean retrieving = false;
public ImageProxy(URL url) { imageURL = url; }
public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
} else {
return 800;
}
}
public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
} else {
return 600;
}
}
public void paintIcon(final Component c, Graphics g, int x, int y) {
if (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() {
public void run() {
try {
imageIcon = new ImageIcon(imageURL, "CD Cover");
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
}
我們先接著看
import java.awt.*;
import javax.swing.*;
class ImageComponent extends JComponent {
private Icon icon;
public ImageComponent(Icon icon) {
this.icon = icon;
}
public void setIcon(Icon icon) {
this.icon = icon;
}
public 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);
}
}
最後我們來看主程式其中呼叫的程式碼
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);
現在我們來看看這虛擬代理到底是如何執行的。
1.我們先初始化了一個代理,只是將遠端url串構造到代理中。
2.然後我們將icon這個代理委託到ImageComponent中。
3.到上述為止,我們依然沒有看到任何代理的影子,接著就是見證神奇的一面。接著我們呼叫新增到JFrame視窗,由於呼叫這個方法,會先呼叫ImageComponent中的paintComponent,接著我們依次呼叫 icon.getIconWidth()和icon.getIconHeight(),最重要我們呼叫代理中的icon.paintIcon(this, g, x, y);
在上述方法呼叫過程中個,代理中的ImageIcon物件均為null,這個if-else就很明顯,這邊在詳解一下paintIcon()方法,由於第一次呼叫imageIcon為空,然後我們建立一個執行緒類,然後將這個執行緒類啟動,這個執行緒類到底做了什麼呢?看下面程式碼:
imageIcon = new ImageIcon(imageURL, "CD Cover");
c.repaint();
這邊等待imageIcon載入成功後,重新重新整理ImageCompontent方法。最後顯示圖片。
最後我們將介紹一個代理,利用JAVA-API實現的一個動態代理,這邊我們將實現保護代理。類圖與普通的代理有點不同
代理變成兩個類。然後讓我們看一下具體的例子,我們希望去保護一個人的具體資訊,這些資訊只有本人能夠進行修改,而評價只有非本人進行修改。
首先我們先實現一個介面:
public interface PersonBean {
String getName();
String getGender();
String getInterests();
int getHotOrNotRating();
void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setHotOrNotRating(int rating);
}
接著我們要實現兩個InvocationHandler,一個是擁有者,另一個是非擁有者的:
package headfirst.proxy.javaproxy;
import java.lang.reflect.*;
public class OwnerInvocationHandler implements InvocationHandler {
PersonBean person;
public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {
try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
throw new IllegalAccessException();
} else if (method.getName().startsWith("set")) {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
package headfirst.proxy.javaproxy;
import java.lang.reflect.*;
public class NonOwnerInvocationHandler implements InvocationHandler {
PersonBean person;
public NonOwnerInvocationHandler(PersonBean person) {
this.person = person;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {
try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
throw new IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
接著我們將看到我們如何建立兩個代理
package headfirst.proxy.javaproxy;
import java.lang.reflect.*;
import java.util.*;
public class MatchMakingTestDrive {
Hashtable datingDB = new Hashtable();
public static void main(String[] args) {
MatchMakingTestDrive test = new MatchMakingTestDrive();
test.drive();
}
public MatchMakingTestDrive() {
initializeDatabase();
}
public void drive() {
//從資料庫中取出一個數據
PersonBean joe = getPersonFromDatabase("Joe Javabean");
//建立一個擁有者物件
PersonBean ownerProxy = getOwnerProxy(joe);
//呼叫getter方法
System.out.println("Name is " + ownerProxy.getName());
//呼叫setter方法
ownerProxy.setInterests("bowling, Go");
System.out.println("Interests set from owner proxy");
try {
//試著呼叫評價方法,會丟擲異常
ownerProxy.setHotOrNotRating(10);
} catch (Exception e) {
System.out.println("Can't set rating from owner proxy");
}
System.out.println("Rating is " + ownerProxy.getHotOrNotRating());
//建立一個非擁有者物件
PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
//
System.out.println("Name is " + nonOwnerProxy.getName());
try {
//呼叫setter方法,會丟擲異常
nonOwnerProxy.setInterests("bowling, Go");
} catch (Exception e) {
System.out.println("Can't set interests from non owner proxy");
}
//呼叫評價方法,沒有問題
nonOwnerProxy.setHotOrNotRating(3);
System.out.println("Rating set from non owner proxy");
System.out.println("Rating is " + nonOwnerProxy.getHotOrNotRating());
}
PersonBean getOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
}
PersonBean getNonOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}
PersonBean getPersonFromDatabase(String name) {
return (PersonBean)datingDB.get(name);
}
void initializeDatabase() {
PersonBean joe = new PersonBeanImpl();
joe.setName("Joe Javabean");
joe.setInterests("cars, computers, music");
joe.setHotOrNotRating(7);
datingDB.put(joe.getName(), joe);
PersonBean kelly = new PersonBeanImpl();
kelly.setName("Kelly Klosure");
kelly.setInterests("ebay, movies, music");
kelly.setHotOrNotRating(6);
datingDB.put(kelly.getName(), kelly);
}
}
總結:
1.代理模式還有很多變種,例如快取代理,同步代理,防火牆代理,和寫入時複製代理
2.代理在結構上類似裝飾者,但是目的不同哦,裝飾者為物件加上行為,而代理是控制行為。
3.代理模式是要實現介面,而介面卡是要改變介面的實現。
4.代理模式也會造成設計中類的數目增加。