`

《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.代理模式也会造成设计中类的数目增加。 

  • 大小: 65.7 KB
  • 大小: 89.8 KB
  • 大小: 89.2 KB
  • 大小: 156.9 KB
  • 大小: 148.8 KB
  • 大小: 183.9 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics