• [ << ]
  • [ 0 ]
  • [ 1 ]
  • [ 2 ]
  • [ 3 ]
  • [ >> ]
Jun '04
28
Decorator Pattern (Wrapper)

Het Decorator pattern wordt door the Gang of Four [GoF] gedefinieerd als : "Attach additional capabilities to objects dynamically". Dit is precies wat er gebeurt wanneer we een Pointcut met Advice definiƫren op een klasse. Het gedrag van een methode wordt veranderd of helemaal vervangen. Het klasse diagram toegepast op ons voorbeeld ziet er als volgt uit :
                 
 /---------<<Fordo>>
 | 	 	^
 |	 	|
 | 	------------------
 | 	| |
 \-<>FordoWrapper FordoImpl 	 

                
In de interface (Fordo) alle methoden declareert (i.e. disposeOfThing()). FordoImpl doet het eigenlijke werk, terwijl de FordoWrapper alle binnenkomende requests doorgeeft aan een volgende implementatie van de Fordo interface. Nadat er natuurlijk een regel naar het log is weg geschreven :-)

MiddleEarth.main() -> Fordo.disposeOfThing() -> FordoWrapper.disposeOfThing() -> FordoImpl.disposeOfThing()

In Source Code :
                 
 public interface Fordo {
	public void disposeOfThing();
 }
 
 public class FordoWrapper implements Fordo {
	private Fordo myDelegate;
	public FordoWrapper(Fordo delegate) { 
	 myDelegate=delegate; 
	};
	public void disposeOfThing() {
	 //
	 // Really meaningful log enty.
	 //
	 System.out.println("Fordo tries to dispose of the Thing.");
	 //
	 // Call the object actually implementing the behaviour.
	 //
	 myDelegate.disposeOfThing();
	 //
	}
 }
 public class FordoImpl implements Fordo {
	public void disposeOfThing() {
	 throw new RuntimeException("I Cannot ! Its too precious.");
	}
 }
 
 public static void main(String[] args) {
	// Create Fordo
	Fordo theHalfling = new FordoWrapper(new FordoImpl());
	// Dispose of the Thing.
	theHalfling.disposeOfThing();
	//
 }

                
Heel leuk, zul je zeggen, dat je op deze manier gedrag toe kan voegen aan bestaande objecten maar er kleven nogal wat nadelen aan vast :
  • In plaats van 1 enkele klasse heb ik nu 2 klassen en een interface.
  • De Wrapper klasse bestaat voor het grootste deel uit het forwarden van methode aanroepen. Cut copy en paste software.
  • Ik moet met het handje de objecten in elkaar steken om het gewenste gedrag te krijgen.
Scherp observatie vermogen ! De interface is noodzakelijk voor deze implementatie, daar ontkom je niet aan. De uiteindelijke implementatie ook (anders gebeurt er niets :-) De Wrapper daar in tegen kan voor een groot deel generiek gemaakt worden door middel van code generatie of een oplossing d.m.v. reflectie. Dit elimineert dan gelijk punt 2. Punt 3 kan worden weg geabstraheerd door middel van het Factory pattern (hierover later meer).

Dynamische wrappers m.b.v. de Proxy class

Zorgvuldig weggestopt in de java.lang.reflect package zit een class genaamd Proxy. Samen met de interface InvocationHandler vormt hij een krachtig mechanisme om dynamisch interfaces samen te voegen en te implementeren. De meest relevante methode van de Proxy class is :

Proxy.newProxyInstance(ClassLoader l, Class[] interfaces, InvocationHandler target);

Deze methode genereert dynamisch een class op basis van de interfaces die gespecificeerd worden als argument. De implementatie van deze class forward alle calls naar de mee gespecificeerde InvocationHandler. De interface InvocationHandler zelf heeft slechts 1 methode :

public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable;

Dat wil zeggen iedere uitgevoerde methode op de proxy resulteert in een aanroep op de invoke method van InvocationHandler, met daarin methode die uitgevoerd moet worden en de bijbehorende argumenten. Als resultaat kan een object terug gegeven worden. De proxy class draagt zorg voor het converteren naar/van primitieven als dat noodzakelijk mocht zijn.

Met deze twee hulpjes is het mogelijk om een generiek delegatie object te maken dat automatisch alle methode aanroepen doorstuurt :
                 
 public static class Wrapper implements InvocationHandler {
	private Object myDelegate; 
	public Wrapper(Object delegate) {
	 myDelegate=delegate;
	}
	public Object invoke( Object proxy, 
	 Method method, 
	 Object[] args) throws Throwable {
	 //
	 // Log something (Before 'Advice')
	 //
	 System.out.println("Now invoking : "+method.getName());
	 //
	 // Find the same method on the target object.
	 //
	 Method m=myDelegate.getClass().getMethod(
	 method.getName(),
	 method.getParameterTypes());
	 //
	 // Invoke it.
	 //
	 return m.invoke(myDelegate,args);
	}
 }

                
Het construeren van de object keten gaat nu iets technischer :
                
 Fordo theHalfling=(Fordo)Proxy.newProxyInstance(
		Fordo.class.getClassLoader(), // <- classloader
		new Class[] { Fordo.class }, // <- interfaces to implement
		new Wrapper(new FordoImpl()) // <- invocationhandler&target.
 );

                
De wrapper bestaat nu zelf uit 2 objecten : de Proxy en de Wrapper. Jawel en weer een abstractie laag er tussen. Maar goed, deze laag scheelt wel een heleboel cut copy en paste werk :-)

MiddleEarth.main() -> Fordo.disposeOfThing() -> Proxy$1.disposeOfThing() -> Wrapper.invoke(..) -> FordoImpl.disposeOfThing()

  • [ << ]
  • [ 0 ]
  • [ 1 ]
  • [ 2 ]
  • [ 3 ]
  • [ >> ]