The Proxy Pattern provides a surrogate or placeholder for another object to control access to it.
The Proxy Pattern provides a representative for another object in order to control the client’s access to it. There are a number of ways it can manage that access.
- Remote Proxy: manages interaction between a client and a remote object.
- Virtual Proxy: controls access to an object that is expensive to instantiate.
- Protection Proxy: controls access to the methods of an object based on the caller.
Other variants of the Proxy Pattern are the following:
- Firewall Proxy: controls access to a set of network resources, protecting the subject from malicious clients. This is used in corporate firewall systems.
- Smart Reference Proxy: provides additional actions whenever a subject is referenced, such as counting the number of references to an object. In C++ this is a shared smart pointer.
- Caching Proxy: provides t3emporary storage for results of operations that are expensive. It can also allow multiple clients to share the results to reduce computation or network latency. This is often seen in web server proxies as well as content management and publishing systems.
- Synchronization Proxy: provides safe access to a subject from multiple threas. This proxy is used in JavaSpaces, where it controls synchronized access to an underlying set of objects in a distributed environment.
- Complexity Hiding Proxy: hides the complexity of and controls access to a complex set of classes. This is sometimes called the Facade Proxy. The Complexity Hiding Proxy differs from the Facade Pattern in that the proxy controls access, while the Facade Pattern just provides an alternative interface.
- Copy-On-Write Proxy: controls the opying of an object by deferring the copying of an object until it is required by a client. This is a variant of the Virtual Proxy.

Remote Proxy Pattern Example
The following example shows a company that has GumballMachine
s which they want to monitor remotely.
To do this, we use a GumballMonitor
that will talk to a proxy, which knows how to talk to the remote GumballMachines
and how to return a report from the state of the GumballMachine
.
The first step is to turn the GumballMachine
into a service that can handle remote requests from clients.
To do that, we need to:
- Create a remote interface for the
GumballMachine
. This will provide a set of methods that can be called remotely. - Make sure all the return types in the interface are serializable.
- Implement the interface in a concrete class.
Let’s start with the remote interface:
import java.rmi.*;
public interface GumballMachineRemote extends Remote {
public int getCount() throws RemoteException;
public String getLocation() throws RemoteException;
public State getState() throws RemoteException;
}
The State
class is not yet serializable. To fix this we use the following implementation:
import java.io.*;
public interface State extends Serializable {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
Each State
has a reference to a GumballMachine
which should not be serialized.
In Java this can be achieved with the transient
keyword as shown with the
following NoQuarterState
class.
public class NoQuarterState implements State {
private static final long serialVersionUID = 2L;
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";
}
}
The GumballMachine
class needs to act as a service and handle request coming over the network.
Therefore the GumballMachine
needs to implement the GumballMachineRemote
interface.
import java.rmi.*;
import java.rmi.server.*;
public class GumballMachine
extends UnicastRemoteObject implements GumballMachineRemote
{
private static final long serialVersionUID = 2L;
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 #2014");
result.append("\nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n");
result.append("Machine is " + state + "\n");
return result.toString();
}
}
To let the GumballMachine
serve requests, we need to make sure we register it with the
RMI registry, which is Java’s Remote Method Invocation registry. This allows clients to locate this service. The following test code takes care of the registration:
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();
}
}
}
The constructor of the GumballMachine
can throw exceptions because it extends the Java UnicastRemoteObject
class.
Therefore the creation of GumballMachine
needs to be placed inside a try
and catch
statement.
The call to Naming.rebind
publishes the GumballMachine
stub under the name gumballmachine
.
For the GumballMonitor
to work over the network, we have to rely on the remote interface GumballMachineRemote
.
This class will also catch any exceptions that might happen when requests over the network happen.
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();
}
}
}
To monitor a bunch of remote GumballMachines
we can use the following test program:
import java.rmi.*;
public class GumballMonitorTestDrive {
public static void main(String[] args) {
String[] location = {"rmi://santafe.mightygumball.com/gumballmachine",
"rmi://boulder.mightygumball.com/gumballmachine",
"rmi://seattle.mightygumball.com/gumballmachine"};
if (args.length >= 0)
{
location = new String[1];
location[0] = "rmi://" + args[0] + "/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++) {
monitor[i].report();
}
}
}
It uses the locations to monitor to create an array of GumballMonitor
s.
Then it uses a GumballMachineRemote
proxy to iterate through the state of each GumballMachine
.
Here is the output after running the two test programs:
$ java GumballMonitorTestDrive
Gumball Machine: santafe.mightygumball.com
Current inventory: 99 gumballs
Current state: waiting for quarter
Gumball Machine: boulder.mightygumball.com
Current inventory: 44 gumballs
Current state: waiting for turn of crank
Gumball Machine: seattle.mightygumball.com
Current inventory: 187 gumballs
Current state: waiting for quarter
Comments