Cyclische dependencies @ runtime
By: Frank Verbruggen, 16 April 2009In een gelaagd project zijn er meestal aparte lagen gedefinieerd voor services, domein logica en data access. Stel nu dat er een noodzaak is om vanuit een lager gelegen laag gebruik te maken van functionaliteit die thuis hoort in een hoger gelegen laag. Bijvoorbeeld omdat er in de domein objecten een noodzaak is om gebruik te maken van een service. Hoe zorg je er dan voor dat je nette object georienteerde code houdt, zonder dat je compile time dependencies krijgt tussen je projecten / lagen? Het hier gepresenteerde design pattern is een elegante object georienteerde oplossing voor het bovenstaande probleem.
Gegeven een DomeinObject als object uit de lagere laag, en een ServiceImplementation als object uit de hogere laag. De serviceImplementation heeft een een interface methode ’someMethod()’ uit de ServiceInterface die benodigd is om de methode ‘businessMethod()’ uit het DomeinObject te implementeren (zie figuur 1, originele klasse diagram).
N.B. Het design pattern is uitgewerkt met domein objecten en service implementaties, maar dat is slechts een invulling.
—————————————————————-

—————————————————————-
Figuur 1, originele klasse diagram
—————————————————————-
Los dit als volgt op. Trek de ServiceInterface uit de hogere laag, en zet deze in de lagere laag. Maak een RequiredInterfaceFactory die een instantie van de ServiceInterface kan bevatten. Initialiseer bij het opstarten van je applicatie vanuit de bovenste laag de RequiredInterfaceFactory met de ServiceImplementation (dit kan bijvoorbeeld goed met de Spring configuratie uit Listing 1, Spring configuratie). En implementeer de businessMethod door de aanroep naar de ServiceImplementation als volgt te abstraheren:
RequiredInterfaceFactory.getInstance().getServiceInterface().someMethod();
Het klasse diagram ziet er dan uit zoals weergegeven in Figuur 2, klasse diagram.
—————————————————————-
—————————————————————-
Listing 1, Spring configuratie
—————————————————————-
—————————————————————-

—————————————————————-
Figuur 2, klasse diagram
—————————————————————-
Voordelen en nadelen
Het gebruik van dit design pattern biedt de volgende voordelen:
- Compile time dependencies zijn niet nodig
- At runtime kunnen interface methoden van bovenliggende klassen toch gebruikt worden
- Ieder object kan gebruik maken van de interface, ongeacht in welk package het zit, dus het ophalen van de interface kan in de code altijd op dezelfde manier gebeuren
Het gebruik van dit design pattern biedt de volgende nadelen:
- Je moet de RequiredInterfaceFactory injecteren met de ServiceImplementation voordat de rest van de applicatie aangesproken wordt

19 April 2009 om 11:12 am
Wat je ook kunt doen, is de implementatie met behulp van reflection opzoeken en instantiëren. Dit zorgt ervoor dat je geen cyclische dependencies hebt en je hebt niet het genoemde nadeel dat er injectie moet plaatsvinden voor de eerste call.
21 April 2009 om 12:38 pm
Is absoluut een mogelijkheid, maar biedt het bijkomende nadeel dat als je implementatie ook injection behoeft deze dan niet gemakkelijk met reflection te instantieren is.
18 May 2009 om 7:18 pm
Here I go again
Maar ik denk dat de probleemomschrijving niet deugt. Het lagenpatroon dient om je code te structureren. Hierbij behoor je uit te komen op lagen waarbij afhankelijkheden van boven naar beneden lopen en niet vice versa. Wanneer je ontdekt dat je in een domeinlaag een service uit de bovenliggende servicelaag wilt gebruiken, dan toont dat aan dat je lagenindeling niet deugt. De te gebruiken “service” hoort wellicht gewoon in zijn geheel in de domeinlaag thuis.
En als je je ontwerp vervolgens beter structureert ontdek je wellicht dat je verschillende typen componenten hebt die allemaal een eigen verantwoordelijkheid hebben, maar die elkaar wel allemaal nodig hebben en die daarom allemaal in 1 laag thuishoren. Zo kom je van het “verbod” op wederzijdse compile time afhankelijkheden af en heb je ook geen geforceerde “oplossingen” nodig. Gooi het domeinobject en de service gewoon in 1 jar, instantieer de service gewoon met new, knoop ze met Spring aan elkaar, doe wat je gewend bent te doen.
Het onterecht in de strijd gooien van patterns veroorzaakt ellende. Het lagen pattern is een pattern dat vaak gebruikt wordt, maar ook vaak incorrect (zie de blog die ik eraan gewijd heb). Het singleton pattern is er ook zo een; veel applicaties die ik zie staan bol van de singletons, maar als je je afvraagt of de bewuste klasse echt perse niet vaker dan een enkele keer mag voorkomen dan blijkt dat toch vrij zelden het geval te zijn. Maar dwaal af…
19 May 2009 om 8:14 am
Met Pieter eens, dit is een grappige manier om een implementatie aan te roepen zonder dat je er een dependency op hebt, meer niet. Ik zou niet graag in een project werken waar dit soort onoverzichtelijke fratsen uitgehaald worden om compile-time dependencies te voorkomen. Als het namelijk zo is dat deze klassen zo close gekoppeld zijn dat ze elkaar moeten aanroepen, dan horen ze gewoon bij elkaar.
JK: Jij moet helemaal stoppen met je “dit kan je ook oplossen door de implementatie op te zoeken met reflectie” haha.
Dit soort problemen zijn nagenoeg altijd op te lossen met refactoring en het verplaatsen van klassen en verantwoordelijkheden.