欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《Head.First设计模式》的学习笔记(15)--代理模式

程序员文章站 2022-05-22 18:37:56
...

 
意图:

为另一个对象提供一个替身或占位符得以访问这个对象。

 

 

结构:

《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上对象的本地代表。


《Head.First设计模式》的学习笔记(15)--代理模式
            
    
    博客分类: 设计模式  
 

 

 

接着我们将介绍虚拟代理,虚拟代理作为创建开销大的对象的代表。虚拟对象经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中,有虚拟对象来扮演对象的替身,对象创建后,代理就会将请求直接委托给对象。

例子:

我们经常会碰到JFrame加载一个大的网络图片,这时候我们就可以先使用代理显示正在加载图片,等图片真正加载好我们才“画”上这个图片,先看类图:


《Head.First设计模式》的学习笔记(15)--代理模式
            
    
    博客分类: 设计模式  
 
 

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实现的一个动态代理,这边我们将实现保护代理。类图与普通的代理有点不同

 


《Head.First设计模式》的学习笔记(15)--代理模式
            
    
    博客分类: 设计模式  
 代理变成两个类。然后让我们看一下具体的例子,我们希望去保护一个人的具体信息,这些信息只有本人能够进行修改,而评价只有非本人进行修改。

首先我们先实现一个接口:

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

  • 《Head.First设计模式》的学习笔记(15)--代理模式
            
    
    博客分类: 设计模式  
  • 大小: 65.7 KB
  • 《Head.First设计模式》的学习笔记(15)--代理模式
            
    
    博客分类: 设计模式  
  • 大小: 89.8 KB
  • 《Head.First设计模式》的学习笔记(15)--代理模式
            
    
    博客分类: 设计模式  
  • 大小: 89.2 KB
  • 《Head.First设计模式》的学习笔记(15)--代理模式
            
    
    博客分类: 设计模式  
  • 大小: 156.9 KB
  • 《Head.First设计模式》的学习笔记(15)--代理模式
            
    
    博客分类: 设计模式  
  • 大小: 148.8 KB
  • 《Head.First设计模式》的学习笔记(15)--代理模式
            
    
    博客分类: 设计模式  
  • 大小: 183.9 KB