About Proxy Pattern, The Standard and Beyond

So, what this article is about? This is about programming, designing software solution, the code will be written in Java language.

Proxy is a class functioning as interface to something else. When a part of code call a method on a proxy, proxy will simply forward it to other class behind the schene. That is the concept, the implementation is basically free. But, there is design pattern for it. Design pattern is general reusable solution to commonly occuring problem.

The Standard

The standard pattern is:

  1. Create an interface
  2. Create class(es) that implements the interface
  3. Client code will declare a variable of type as declared interface
  4. Fill the variable with concrete implemented class object

Example: We created interface like follow, named Animal.

public interface Animal {
   public String getName();
   public void setName(String name);
   public void tellMyName();
}

Then we create two classes that implements the Animal interface. First we name it Amoeba.

public class Amoeba implements Animal {
   private String name;
   public String getName() {
      if (name == null) return "Amoeba is so primitive. It doesn't have even a name.";
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public void tellMyName() {
      System.out.println("I'am "+name+". I just have one cell.");
   }
}

Second we name it Tyrannosaur.

public class Tyrannosaur implements Animal {
   private String name;
   public String getName() {
      if (name == null) return "Well, you haven't give it a name.";
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public void tellMyName() {
      System.out.println("Groaaarrr. I am "+name+" The Great!!!");
   }
}

Then, in the client code we could use either Amoeba or Tyrannosaur instance as Animal type variable. We could use either one interchangable whenever we need.

Animal animal = new Amoeba();
animal.setName("Agus");
animal.tellMyName();
animal = new Tyrannosaur();
animal.setName("Toni");
animal.tellMyName();

Those lines will produce output like below:

I'am Agus. I just have one cell.
Groaaarrr. I am Toni The Great!!!

Or, we can create a special class to manage two (or more) instances and use one of them just by set value to a variable. I created a class named OldStyleProxy. This class in simple by-the-book implementation of the Proxy Pattern concept.

public class OldStyleProxy {
   public static int index = 0;
   private ArrayList<Animal> animals;

public OldStyleProxy() {
      animals = new ArrayList<Animal>();
   }
   public void addProxyInstance(Animal animal) {
      animals.add(animal);
   }
   public Animal getCurrentAnimal() {
      if (index >= animals.size()) throw new ArrayIndexOutOfBoundsException("We have only put "+animals.size()+" animal(s).");
      return animals.get(index);
   }
}

The we can use it as follow. So we just need to change the value of OldStyleProxy.index to 0 to have the Amoeba object or 1 to get the Tyrannosaur object. Both of them are ready loaded in the memory.

OldStyleProxy proxy = new OldStyleProxy();
Amoeba amoeba = new Amoeba();
amoeba.setName("Agus");
Tyrannosaur tyrannosaur = new Tyrannosaur();
tyrannosaur.setName("Toni");
proxy.addProxyInstance(amoeba);
proxy.addProxyInstance(tyrannosaur);
OldStyleProxy.index = 0;
proxy.getCurrentAnimal().tellMyName();
OldStyleProxy.index = 1;
proxy.getCurrentAnimal().tellMyName();

However, this implementation is not flexible. Why? We need to declare Amoeba and Tyrannosaur as Animal implementation. This is the ideal case. Unfortunately, sometime we meet unideal conditions. For example we have existing implementation of Cat and Dog that came from well known library that we cannot alter. So how can we deal with it?

Next Level Proxy

So, the case is:

  1. We have an existing implementation of Cat from library1, and implementation of Dog from library2. Both of them might have the same methods.
  2. Then we create the new interface Animal (as above) based on those methods
  3. We want Amoeba, Tyrannosaur, Cat and Dog is compatible with Animal interface

How come? We have Amoeba and Tyrannosaur implements Animal, so we simply can do the above concept. But we cannot do that for Cat and Dog.

Animal cat = (Animal) new Cat();
cat.setName("Cahyo");
cat.tellMyName();

Those lines will compiled, but will raise a ClassCastException. Problem will also occured in below lines. These lines wouldn’t even compiled. The method addProxyInstance is not applicable to Cat instance.

Cat cat = new Cat();
cat.setName("Cahyo");
OldStyleProxy proxy = new OldStyleProxy();
proxy.addProxyInstance(amoeba);
proxy.addProxyInstance(tyrannosaur);
proxy.addProxyInstance(cat); // this line will error

So this is a problem. This problem need solution.

My solution is to use Reflection and Generic Type. The concept is to use reflection to execute methods.

  1. Create a proxy with Builder Pattern. So the proxy will have method named build() which return a Generic typed object. This means, we will supply the class type to be used as returning type for build() method.
  2. We will accept any given object as proxy instance.
  3. Create InvocationHandler to deal with methods call. So, any method call of given objects will be executed by an InvocationHandler.

Here is the next level proxy implementation:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;

public class Proxify<T> {
public static int index;

private ArrayList<Object> proxyInstances;
private Class<T> returningClass;

private ArrayList<String> methodNames;

public Proxify(Class<T> returningClass) {
proxyInstances = new ArrayList<Object>();
this.returningClass = returningClass;

methodNames = new ArrayList<String>();
detectMethodNames();
}

private void detectMethodNames() {
methodNames = getMethodNames(returningClass);
}

private ArrayList<String> getMethodNames(Object object) {
return getMethodNames(object.getClass());
}

private ArrayList<String> getMethodNames(Class<?> theClass) {
Method[] methods = theClass.getDeclaredMethods();
ArrayList<String> result = new ArrayList<String>();
for (Method method:methods) {
result.add(method.getName());
}
return result;
}

public Proxify addProxyInstance(Object instance) {
proxyInstances.add(instance);

ArrayList<String> methods = getMethodNames(instance);

for (String methodName:methodNames) {
if (!methods.contains(methodName)) throw new IllegalArgumentException("Proxy instance don't have method "+methodName);
}

return this;
}

public T build() throws ClassNotFoundException {
ProxyHandler handler = new ProxyHandler();
T result = (T) (new ProxiedObjectFactory<T>().create(returningClass, handler));
return result;
}

private Object getObject(int index) {
return proxyInstances.get(index);
}

private class ProxyHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (Proxify.index < 0 || Proxify.index >= proxyInstances.size())
throw new IndexOutOfBoundsException("You have "+proxyInstances.size()+" proxy instances. Your index ("+Proxify.index+") is out of bound.");

execute(Proxify.index, method, args);
return null;
}

private void execute(int index, Method method, Object[] args) throws Throwable {
Object object = getObject(index);

if (object instanceof Proxy) {
Proxy proxy = (Proxy) object;
proxy.getInvocationHandler(proxy).invoke(proxy, method, args);
} else {
Class<?> theClass = Class.forName(object.getClass().getName());
Method theMethod = theClass.getMethod(method.getName(), getTypes(args));
theMethod.invoke(object, args);
}
}

private Class[] getTypes(Object[] arguments) {
if (arguments == null) return new Class[]{};

Class[] result = new Class[arguments.length];

int i = 0;
for (Object object: arguments) {
result[i] = object.getClass();
i++;
}
return result;
}

}
}

Now, the proxy is able to deal with Amoeba and Tyrannosaur (which are implementations of Animal), even with Cat and Dog. They all will be treated like instance of Animal, equally.

Amoeba amoeba = new Amoeba();
amoeba.setName("Agus");

Tyrannosaur tyrannosaur = new Tyrannosaur();
tyrannosaur.setName("Toni");

Cat cat = new Cat();
cat.setName("Cahyo");

Dog dog = new Dog();
dog.setName("Danang");

Proxify<Animal> proxify = new Proxify(Animal.class);
Animal result = (Animal) proxify
.addProxyInstance(amoeba)
.addProxyInstance(tyrannosaur)
.addProxyInstance(cat)
.addProxyInstance(dog)
.build();

Proxify.index = 0;
result.tellMyName();

Proxify.index = 1;
result.tellMyName();

Proxify.index = 2;
result.tellMyName();

Proxify.index = 3;
result.tellMyName();

Give it a try!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.