• [ << ]
  • [ 0 ]
  • [ 1 ]
  • [ 2 ]
  • [ 3 ]
  • [ >> ]
Jun '04
28
Interfaces samenvoegen (Introduction)
Met het bovenstaande is het goed mogelijk om het gedrag van een object te veranderen. Echter het toevoegen van gedrag zoals met Introduction in AOP gedaan is iets heel anders. Hoe kunnen we dit realiseren ?

Ook hier komt de Proxy class ons te hulp. Bij het construeren van een nieuwe proxy is het mogelijk om meerdere interfaces te specificeren. En als we dan in de Wrapper wat slimmigheid inbouwen zodat deze kan kiezen tussen verschillende implementerende objecten dan moet het kunnen werken. Mmm.. begint verdacht veel op multiple inheritance te lijken niet ?

De implementatie heeft in plaats van 1 delegate nu een array van mogelijke targets, waarbij voor iedere target gekeken wordt of the aangeroepen methode kan worden uitgevoerd. Zo ja, dan wordt daar mee verder gegaan. Is er geen implementatie beschikbaar van een methode, dan wordt een exceptie gegooid.
                 
	//
 // Find the appropriate target. 
 //
 Object target=null
	for (int t=0;t<targets.length;t++) try {
	 targets[t].getClass().getMethod(
			method.getName(),
			method.getParameterTypes()
	 );
	 target=targets[t];
	 break;
	} catch (NoSuchMethodException ex) {
	 continue;
	} 
	//
	if (target==null) 
	 throw new NoSuchMethodException("Method "+method+" is not defined in "+this);
                
Een sneaky detail zijn natuurlijk de methoden die alle objecten gemeenschappelijk hebben zoals toString(), equals() en hashcode(). In de huidige implementatie gaat alles naar de eerste de beste object dat die methode heeft. Wat tot verassingen kan leiden bij Lists en vooral HashMaps.

Implementeren van Observer/Observable d.m.v. Introductie

Stel je voor dat iemand Fordo in de gaten wil houden (Loggum bijvoorbeeld of anders Soraun :-) doormiddel van het Observer/Observable pattern. Het betekent dat aan Fordo de add/remove Observer methoden moeten worden toegevoegd en dat wanneer Fordo probeert van zijn Thing af te komen er een notificatie gestuurd moet worden naar zijn observers. Een vogel, een vliegtuig, nee.. het is.. Super Aspect !
                 
	<<Fordo>>	<<Observable>>--------<<Observer>>
		|		|
		\---Proxy-------/
			| 	/- Pointcut
		/--Wrapper----- Advice
		| 		|
 	FordoImpl		ObservableImpl

                
Het toevoegen van de add/remove method is geen probleem met de huidige implementatie, aangezien ze netjes binnen de ObservableImpl vallen. Echter.. het afvuren van de notificatie is een heel ander verhaal. Van uit een aspect dat getriggerd word door een methode in FordoImpl moet de fire(Observable) methode van ObservableImpl uitgevoerd worden. Hiertoe heeft het Advice toegang nodig tot die implementatie en tot de proxy (als argument voor fire()).

Een kort door de bocht implementatie is het meegeven van de proxy aan het Advice plus een lookup methode voor implementaties :
                 
 public interface Source {
	 public Object getInstance(Class c);
 }
                
Concreet gebeurt er nu het volgende wanneer Fordo van zijn Thing probeert af te komen :

                 
 MiddleEarth.main()->Fordo.disposeOfThing()-> 
 Proxy$1.disposeOfThing()->Wrapper.invoke(..)-> 
 Advice.around(..)->
	Advice.before(..)->ObservableImpl.fire(..)
	Method.invoke(..)->FordoImpl.disposeOfThing()
                

Factory Pattern

Als laatste, het in elkaar steken van implementatie en proxy objecten. Dat doe je natuurlijk niet zelf. Je wilt niet iedere keer een hele rij met proxies en haakjes en argumenten en implementaties in elkaar typen als je een nieuw object maakt. Dat delegeer je aan een Factory :
                 
 public interface Factory {
	 public Object newInstance(Class interface);
 }
                
Duuh. En de implementatie dan ? Dat kan je vast wel zelf :-) Of je kijkt in de source code die bij dit artikel hoort.

Belangrijke aspecten (grin) van de Factory zijn :
  • welke implementaties/interfaces horen bij een interface die gebouwd moet worden.
  • welke pointcuts/advices horen bij die interface.
Als je dit recursief implementeert en de boel in een mooie XML gooit kan je er hele leuke dingen mee doen.

Wat zou er gebeuren als deze interface ook gewrapped wordt ??

Conclusie

Ze zeggen wel eens dat Software Engineering de kunst is van het oplossen van problemen door het toevoegen van nog één abstractie laag. En nog één. En nog één. Ik denk dat ons demonstatie frameworkje daar een uitstekend voorbeeld van is.

In dit artikel hebben we kort naar verschillende implementaties van AOP frameworks gekeken en hebben we een eigen implementatie gemaakt op basis van het Decorator/Wrapper pattern, technisch gerealiseerd door middel van een Dynamic Proxy. Wrappers worden om de eigenlijke implementatie heen gelegd waarmee de mogelijkheid ontstaat om het gedrag van een object aan te passen. Door het toevoegen van Pointcuts (triggers) en Advice (wat er moet gebeuren als een trigger afgaat) is dit makkelijk te definiëren buiten de Wrapper om.

Als we dit dan combineren met de mogelijkheid om meerdere interfaces te kunnen mappen op meerdere implementerende objecten (niet noodzakelijkerwijs 1 op 1) dan ontstaat een framework waarmee Aspecten gerealiseerd kunnen worden.

Als laatste wordt het samenvoegen van de objecten weggeabstraheerd achter een Factory object.

Voordelen van deze implementatie zijn :
  • Compact, dus snel te begrijpen.
  • Redelijk compleet, de belangrijkste elementen van AOP zijn vertegenwoordigd.
Nadelen van deze implementatie zijn :
  • Performance. Het gebruik van de Proxy class is een slordige 1000 keer langzamer dan een gewone method call. Natuurlijk valt er heel wat te verbeteren, zoals het niet iedere keer gebruiken van de getMethod() methode, maar eenmaal gevonden het resultaat opslaan in een Map (scheelt meer dan de helft!), maar opschieten doet het nog steeds niet.
  • Je moet interfaces casten. (Alhoewel je natuurlijk samengestelde interfaces kan declareren..)
  • Super generieke klassen maken het onoverzichtelijk debuggen.
  • Oppassen bij het gebruik van equals() en andere methoden die alle objecten gemeenschappelijk hebben.
Kan je dit gebruiken voor een serieuze toepassing ? Ik denk het niet. Maar probeer vooral mijn ongelijk te bewijzen :-)

De complete source (5 klassen !) en demo code is hier te downloaden.

Links
AspectJ eclipse.org
JBoss AOP www.jboss.org
AspectWerkz aspectwerkz.codehaus.org
Nanning nanning.codehaus.org
  • [ << ]
  • [ 0 ]
  • [ 1 ]
  • [ 2 ]
  • [ 3 ]
  • [ >> ]