Inversion of control, Dependency Injection en Service Locator pattern (deel 1)
379

Inversion of control, Dependency Injection en Service Locator pattern (deel 1)
Handmatige dependency injection
Het Dependency Inversion Principle zegt dat een bepaalde class geen afhankelijkheid mag hebben van lagergelegen classes in de applicatie:

ioc-1

In het bovenstaande voorbeeld zie je dat de Presentation class afhankelijk is van de Service class welke op zijn beurt weer afhankelijk is van de Repository class. Deze afhankelijkheid (tight coupling) wordt gecreëerd op het moment dat een class zelf een instantie van een andere class aanmaakt. In een architectuur die op deze manier functioneert zijn objecten niet helder van elkaar gescheiden waardoor, naarmate de applicatie groeit, er allerlei dwarsverbanden en afhankelijkheden ontstaan die het lastig maken de applicatie uit te breiden en/of te onderhouden.

Voldoen aan het Inversion Principle met interfaces
Om te voldoen aan het dependency inversion principle is het in de eerste plaats belangrijk dat een class niet afhankelijk is van een implementatie, maar van een interface. Hiervoor leg je contracten in je software vast die het mogelijk maken om classes te testen (door afhankelijkheden te mocken) en om flexibel implementaties te wisselen. Stel bijvoorbeeld dat de bovenstaande applicatie zowel met een Oracle- als een SQL-database moet kunnen samenwerken, dan kun je door een IRepository te introduceren eenvoudig allebei de implementaties bouwen en vanuit configuratie de ene of de andere implementatie selecteren:

ioc-2

De service communiceert nu met een interface en niet langer met een concreet object en daardoor kan ik iedere implementatie van de interface (contract) aanbieden die gewenst is. Belangrijk wordt het nu alleen hoe de service deze implementatie gaat ontvangen.

Implementaties handmatig injecteren
Om de objecten los te koppelen moet gebruik worden gemaakt van dependency injection. Hierbij worden implementaties in een class geïntroduceerd door ze te injecteren. Injecteren kan op verschillende manieren gebeuren, zoals bijvoorbeeld via constructor injectie of via parameter c.q. methode injectie.

ioc-3

In het bovenstaande voorbeeld zie je dat de Presentation, Service en Repository objecten geen afhankelijkheid meer van elkaar hebben. Als je kijkt naar de Service class dan zie je dat de implementatie van de Repository via de constructor wordt meegegeven. De Service class zelf bepaalt dus niet langer welke implementatie van de Repository gebruikt wordt, zolang de aangeboden implementatie maar voldoet aan het contract (de interface implementeert). Hetzelfde principe geldt voor de Presentation class. In de App class worden nu de juiste implementaties geïnstantieerd en aan elkaar geknoopt.

Voordeel van handmatige dependency injectie
• We zijn nu in staat om de Presentation en Service objecten eenvoudig te unit testen
• De Presentation en Service objecten zijn niet langer afhankelijk van concrete implementaties, maar van interfaces, dus we kunnen verschillende implementaties ondersteunen zonder deze objecten aan te passen.

Nadeel van handmatige dependency injectie
• We hebben het probleem van het creëren van instanties van de Repository en de Service verlegt naar de App class. Dit heeft het probleem deels verlegd maar zeker niet opgelost.
• In dit voorbeeld is de dependency tree heel eenvoudig, maar bij complexe projecten wordt dit een hele lastige klus.
Al met al biedt handmatige dependency injection wel enige verlichting, maar is zeker niet het eindstation om tot goede object georiënteerde software te komen.
Om deze reden zullen we in deel 2 van dit artikel gaan kijken naar het Service Locator pattern om in deel 3 stil te staan bij Inversion Of Control Frameworks (IoC containers).

Auteur: Menno Jongerius, Bergler Competence Center 2016