Proxy Pattern ------------- EXAMPLE 1. Remember the Observer and Observable interfaces for Project 2: public interface Observer { public void update(Observable obs, T arg); } public interface Observable { public void addObserver(Observer o, Filter filter); public void addObserver(Observer o); public void addOneShotObserver(Observer o, Filter filter); public void removeObserver(Observer o) throws ObserverNotFoundException; public void notifyObservers(T arg); public void notifyObservers(); } And recall that a Person implements Observer: public class Person implements Observer { private String name; private java.io.PrintStream out; public Person(String name, java.io.PrintStream out) { this.name = name; this.out = out; } public void update(Observable obs, Newspaper arg) { out.println(name+" received "+arg); } public String toString() { return name; } } Say that we want to change our simulation so that it runs in a distributed setting. That is, we have simulations running on multiple machines on a network, and we want the Publisher to be able to send updates to a person on a remote machine, as well as on local one. First, we have to set up the simulation to be distributed. We can do this by creating a remote server to receive textual update commands from the main simulation server. import java.io.*; import java.net.*; import java.util.*; public class RemoteSimulatorServer { public static final int PORT = Integer.getInteger("port", 8080).intValue(); private Map people = ...; private int port; private BufferedReader in; public RemoteSimulatorServer(int port, Map people) { this.port = port; this.people = people; } private void processUpdate(String personName, String newsDay) { Newspaper paper = new Newspaper(newsDay); Person p = // look up } private void process () throws IOException { ServerSocket s = new ServerSocket(PORT); try { while (true) { // Blocks until a connection occurs: Socket socket = s.accept(); try { in = new BufferedReader (new InputStreamReader (socket.getInputStream())); // get a command String str = in.readLine(); // process it if (str.equals("update")) { StringTokenizer tokSource = new StringTokenizer(in.readLine()); String windowSize; try { String name = tokSource.nextToken(); String day = tokSource.nextToken(); processUpdate(name,day); } catch (Exception e) { System.out.println("Bad command arguments: "+e); } } else { System.out.println("Bad command: " + str); } } finally { in.close(); socket.close(); } } } finally {s.close();} } public static void main(String[] args) throws IOException { RemoteSimulatorServer s = new RemoteSimulatorServer(PORT,...); s.process (); } } How to change the main simulation to take advantage of these remote servers? We will assume that we modify the input to the simulation to have a person's location as part of their name. I.e. subscribe Bob\junkfood.cs.umd.edu:8080 sunday Then the implementation of the simulator will have to take this into account. We assume that the remote simulators are started to know about these people; e.g., we'll start a RemoteSimulatorServer on junkfood.cs.umd.edu listening on port 8080, and initialize it with a list of Person objects, one of whom is named Bob. Now, our goal is: ** When a newspaper is published, how will we notify those Persons that are stored on remote machines? Modify the Publisher so that there are two lists, one of Person objects, and the other a list of string addresses, like "Bob\junkfood.cs.umd.edu:8080 sunday". Normal Person objects are handled as before, and for the remote ones, there is special code that iterates through this list of addresses and sends a command to the appropriate remote server. But this would result in code duplication. Ich. We really want to reuse the Publisher implementation without changing it. EXAMPLE 2. Create a local PROXY, on the machine running the simulation, for each remote Person. This proxy will forward any information it gets to the remote object. This allows the Publisher to remain the same. public class RemotePerson implements Observer { private String name; private int port; private InetAddress addr; private Socket socket; private PrintWriter out; public Person(String name, InetAddress addr, int port) { this.port = port; this.addr = addr; this.name = name; } public void update(Observable obs, Newspaper arg) { openConn(); out.println("update"); out.println(name+" "+arg); closeConn(); } private void openConn () { try { socket = new Socket(addr, port); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true); } catch (IOException e) { System.out.println(e); } } // closes the connection private void closeConn() { try { in.close(); out.close(); socket.close(); } catch (IOException e) {System.out.println(e);} } public String toString() { return name+"\"+addr; } } A RemotePerson would be created by parsing the String mentioned above. E.g. the remote proxy would be Person = new RemotePerson("Bob",InetAddress.getByName("junkfood.cs.umd.edu"),8080); Then, whenever update() was called on this object, it would connect to the remote server, send its message, and then close the connection. The remote server would reconstitute the message and forward it on to the local copy of the object. Java RMI takes care of this sort of thing automatically. See the book for details. PROXY PATTERN DEFINED. See p. 460 in the book for this. Important: how is this different from a decorator? See pps. 471 - 473. EXAMPLE 2. Say we change our simulation so that it's now graphical, and each Person is associated with an image. public class ImagePerson implements Observer { private String name; private Image picture; private java.io.PrintStream out; public Person(String name, java.io.PrintStream out, String imageFile) { this.name = name; this.out = out; this.picture = ... load picture from imageFile ... } public void update(Observable obs, Newspaper arg) { ... paint the picture and message that "name" received "arg" } ... } But what if we have a lot of ImagePersons in the simulation? It might take a long time for the simulation to start up, while its loading pictures from disk. We would prefer to delay loading the picture until the Person is first painted on the screen, to amortize the cost. How could we do this? A simple thing would be to add logic into ImagePerson that delays this directly. But this would just occlude what ImagePerson is actually doing. Instead, we can use a "virtual proxy" as a stand-in for the original object that delays its initialization, which can be expensive. We might also call it a "Lazy proxy." public class LazyImagePerson implements Observer { private String name; private java.io.PrintStream out; private String pictureFile; Person target; public Person(String name, java.io.PrintStream out, String imageFile) { this.name = name; this.out = out; this.pictureFile = imageFile; // picture is not loaded } public void update(Observable obs, Newspaper arg) { if (target == null) { target = new ImagePerson(name,out,pictureFile); // causes picture to get loaded } target.update(obs,arg); } public String toString() { return target.toString(); } // QUESTION: how should we implement the other methods of Object? } Exercise: if we were to change ImagePerson to be able to update its picture at runtime, how would be change LazyImagePerson to do it lazily? Exercise: what if an ImagePerson and its LazyImagePerson proxy could exist in the program at the same time. What problems might come up? EXAMPLE 3. Right now in our simulation we have a separate concept called a Filter that prevents sending information if it does not apply. We are doing it here in the Observable, but we could make things simpler by doing it in the Observers. How could we get rid of Filter entirely and use a Proxy instead? This would be called a "protection proxy" since it "protects" access to the actual object (i.e., you cannot access it unless you pass the filter). New Observable interface: public interface Observable { public void addObserver(Observer o); public void addOneShotObserver(Observer o); public void removeObserver(Observer o) throws ObserverNotFoundException; public void notifyObservers(T arg); public void notifyObservers(); } The notifyObservers implementation would then just run through the list and do updates; no calls to check filters. Here is the FilterPerson proxy: public class FilterPerson implements Observer { private Observer target; private Filter filter; // constructor here ... public void update(Observable obs, Newspaper arg) { if (filter == null || filter.isValid(arg)) target.update(obs, arg); } } Exercise: this won't actually work with OneShotObservers. Why? Is there any easy way to fix this problem? EXAMPLE 4. Java provides native support for proxies: the DynamicProxy class. Let's see how we could do this for the FilterPerson proxy above. public class FilterHandler implements InvocationHandler { Person target; Filter filter public FilterHandler(Filter filter, Person person) { this.target = person; this.filter = filter; } public Object invoke(Object proxy, Method method, Object[] args) { try { if (method.getName().equals("update")) { Newspaper arg = (Newspaper)args[1]; if (filter == null || filter.isValid(arg)) return method.invoke(target,args) } else return method.invoke(target,args); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; } } Here's how you'd create the proxy Person p = new Person(...); Person proxy = Proxy.newInstance(p.getClass().getClassloader(), p.getClass().getInterfaces(), new FilterHandler(new Filter(...),p)); Now just use the proxy in place of the regular person. Notice that the second argument makes the proxy apply to ALL interfaces. I.e., the invoke() method will be called for each method implemented by interfaces implemented by Person. Exercise: when should you use dynamic proxies, rather than making them by hand, as above? One reason is that performance is generally bad; e.g., 10X slower than a normal method call. Assuming that's not a problem, are there any other problems?