Java設計模式(十三):代理設計模式
代理模式可以分為以下四類
- 遠端代理(Remote Proxy):控制對遠端物件(不同地址空間)的訪問,它負責將請求及其引數進行編碼,並向不同地址空間中的物件傳送已經編碼的請求。
- 虛擬代理(Virtual Proxy):根據需要建立開銷很大的物件,它可以快取實體的附加資訊,以便延遲對它的訪問,例如在網站載入一個很大圖片時,不能馬上完成,可以用虛擬代理快取圖片的大小資訊,然後生成一張臨時圖片代替原始圖片。
- 保護代理(Protection Proxy):按許可權控制物件的訪問,它負責檢查呼叫者是否具有實現一個請求所必須的訪問許可權。
- 智慧代理(Smart Reference):取代了簡單的指標,它在訪問物件時執行一些附加操作:記錄物件的引用次數;當第一次引用一個物件時,將它裝入記憶體;在訪問一個實際物件前,檢查是否已經鎖定了它,以確保其它物件不能改變它。
1 遠端代理設計模式
1.1 應用場景
為一個物件在不同地址空間提供區域性代表這樣可以隱藏一個物件存在於不同地址空間的事實,例如:老阮(MrRuan)在地點A,老李在地點B,餐廳櫃檯也在地點乙,那麼老李和老軟住在一起(都在地點甲住),那麼老李就是餐廳(地點B)在老軟與老李住處(地點A)的代表。
1.2 概念
代理設計模式為另一個物件提供一個替身或者佔位符用以控制對這個物件的訪問。使用代理模式建立代表(representative)物件,讓代表物件控制某個物件的訪問,被代理的物件可以是遠端的物件、建立開銷很大的物件或者是需要安全控制的物件。
1.3 Class Diagram
首先是Subject,它是RealSubject和Proxy提供了相同的介面。通過實現同一個介面,Proxy在RealSubject出現的地方取代它。RealSubject才是真正做事的物件,它是被proxy代理和控制訪問的物件。Proxy持有RealSubject的引用,因為Proxy和RealSubject實現相同的介面,所以任何用到RealSubject的地方,都可以用Proxy取代。Proxy也控制了對RealSubject的訪問,在某些情況下,我們可能需要這樣的控制。這些情況包括RealSubject是遠端的物件,RealSubject建立開銷大或者RealSubject需要被保護。
1.4 Implementation
下面展示一個遠端代理設計模式的實現:
1. 遠端代理類介面Subject
public interface GumballMachineRemote extends Remote {
public int getCount() throws RemoteException;
public String getLocation() throws RemoteException;
public State getState() throws RemoteException;
}
2. RealSubject介面正在的實現類
public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote {
private State soldOutState;
private State noQuarterState;
private State hasQuarterState;
private State soldState;
private State winnerState;
private State state;
private int count = 0;
private 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;
this.location=location;
if (numberGumballs > 0) {
state = noQuarterState;
} else {
state = soldOutState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}
public int getCount() {
return count;
}
void refill(int count) {
this.count += count;
System.out.println("The gumball machine was just refilled; it's new count is: " + this.count);
state.refill();
}
void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
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 getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
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();
}
}
3. 通過在“classes”目錄下執行:rmic com.basic.proxypattern.gumball.GumballMachine 命令生成客戶端輔助類Stub和伺服器輔助類Skeleton類。
4. 通過在“classes”目錄下執行:rmiregistry 啟動RMI Registry服務
5. 將我們的GumballMachineRemote註冊到RMI Registry服務中去
public class GumballMachineRemoteTestDrive {
public static void main(String[] args) throws RemoteException {
GumballMachineRemote gumballMachineRemote=null;
int count=0;
if (args.length > 2 ){
System.out.println("GumballMachine <name> <inventory>");
System.exit(0);
}
try {
count=Integer.parseInt(args[1]);
gumballMachineRemote=new GumballMachine(args[0],count);
Naming.rebind("//"+args[0]+"/gumballmachine",gumballMachineRemote);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
6. 這個時候就可以在客戶端呼叫RMI Reigistry註冊過的服務了
public class GumballMointorRemoteTestDrive {
public static void main(String[] args){
try {
GumballMachineRemote machine= (GumballMachineRemote) Naming.lookup("rmi://localhost/gumballmachine");
GumballMonitor gumballMonitor=new GumballMonitor(machine);
gumballMonitor.report();
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
通過呼叫代理的方法,遠端呼叫可以跨過網路,返回字串、整數和State物件。因為我們使用的是代理,呼叫的方法會在遠端執行。
- CEO執行監視器,先取得遠端糖果機的代理,然後呼叫每個代理的getState()、getCount()和getLocation()方法。
- 代理上的getState被呼叫,此呼叫轉發到遠端服務上。遠端伺服器輔助類Skeleton接收到請求,然後轉發給糖果機。
- 糖果機將狀態返回給Skeleton,skeleton將狀態序列化,通過網路傳回給代理,代理將其反序列化,把它當做一個物件返回給監視器。
2. 虛擬代理設計模式
2.1 應用場景
是根據需要建立開銷很大的物件。通過它來存放例項化需要很長時間的真是物件,例如:老阮(MrRuan)在地點A,到餐廳櫃檯(地點B),因為距離遠卻是很費勁,而老李剛好在這裡(地點B)上班,所以讓老李去辦是很可行的辦法。
2.2 概念
虛擬代理作為建立開銷大的物件的代表。虛擬代理經常知道我們真正需要一個物件的時候才建立它。當物件在建立前和建立中時,由虛擬代理來扮演物件的替身。物件建立之後,代理就會將請求直接委託給物件
2.3 Class Diagram
這是一個根據需要建立開銷很大的物件,它可以快取實體的附加資訊,以便延遲對它的訪問。
2.4 Implementation
public class ImageProxy implements Icon {
private ImageIcon imageIcon;
private URL url;
private Thread retrievalThread;
private boolean retrieving = false;
public ImageProxy(URL url) {
this.url = url;
}
@Override
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 , pleas wait...",x+300,y+190);
if(!retrieving){
retrieving=true;
retrievalThread=new Thread(new Runnable() {
@Override
public void run() {
try {
imageIcon=new ImageIcon(url, "CD cover");
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
@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;
}
}
class ImageComponent extends JComponent {
private static final long serialVersionUID = 1L;
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);
}
}
public class ImageProxyTestDrive {
ImageComponent imageComponent;
JFrame frame = new JFrame("CD Cover Viewer");
JMenuBar menuBar;
JMenu menu;
Hashtable<String, String> cds = new Hashtable<String, String>();
public static void main (String[] args) throws Exception {
ImageProxyTestDrive testDrive = new ImageProxyTestDrive();
}
public ImageProxyTestDrive() throws Exception{
cds.put("Buddha Bar","http://images.amazon.com/images/P/B00009XBYK.01.LZZZZZZZ.jpg");
cds.put("Ima","http://images.amazon.com/images/P/B000005IRM.01.LZZZZZZZ.jpg");
cds.put("Karma","http://images.amazon.com/images/P/B000005DCB.01.LZZZZZZZ.gif");
cds.put("MCMXC A.D.","http://images.amazon.com/images/P/B000002URV.01.LZZZZZZZ.jpg");
cds.put("Northern Exposure","http://images.amazon.com/images/P/B000003SFN.01.LZZZZZZZ.jpg");
cds.put("Selected Ambient Works, Vol. 2","http://images.amazon.com/images/P/B000002MNZ.01.LZZZZZZZ.jpg");
URL initialURL = new URL((String)cds.get("Selected Ambient Works, Vol. 2"));
menuBar = new JMenuBar();
menu = new JMenu("Favorite CDs");
menuBar.add(menu);
frame.setJMenuBar(menuBar);
for (Enumeration<String> e = cds.keys(); e.hasMoreElements();) {
String name = (String)e.nextElement();
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(event -> {
imageComponent.setIcon(new ImageProxy(getCDUrl(event.getActionCommand())));
frame.repaint();
});
}
// set up frame and menus
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);
}
URL getCDUrl(String name) {
try {
return new URL((String)cds.get(name));
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}
3. 保護代理設計模式
3.1 應用場景
保護代理模式就是一種可以根據訪問許可權決定客戶可否訪問物件的代理。比如,如果你有一個僱員物件,保護代理允許僱員呼叫物件上的某些方法,經理還可以多呼叫一些其他的方法(像setSlary()),而人力資源的僱員可以呼叫物件上的所有方法。
3.2 概念
按許可權控制物件的訪問,它負責檢查呼叫者是否具有實現一個請求所必須的訪問許可權。保護代理模式可以根據訪問許可權來決定客戶是否可以訪問物件的代理。
3.3 Class Diagram
Java在java.lang.reflect包中有自己的代理支援,利用這個包你可以在執行時動態地建立一個代理類,實現一個或者多個介面,並將方法的呼叫轉發到你指定的類。因為實際的代理類是在執行時建立的,我們稱這個Java技術為動態代理。
InvocationHandler的工作是響應代理的任何呼叫。你可以把InvocationHandler想成是代理收到方法呼叫後,請求實際工作的物件。
3.4 Implementation
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);
}
public class PersonBeanImpl implements PersonBean {
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getInterests() {
return interests;
}
public int getHotOrNotRating() {
if (ratingCount == 0) return 0;
return (rating/ratingCount);
}
public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setInterests(String interests) {
this.interests = interests;
}
public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}
public class NonOwnerInvocationHandler implements InvocationHandler{
private PersonBean personBean;
public NonOwnerInvocationHandler(PersonBean personBean) {
this.personBean = personBean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(method.getName().startsWith("get")){
throw new IllegalAccessException();
}else if(method.getName().startsWith("set")){
throw new IllegalAccessException();