Dependency injection

Dependency injection is een geavanceerd ontwerppatroon uit de informatica dat het mogelijk maakt klassen losjes te koppelen. Dit wil zeggen dat ze data kunnen uitwisselen zonder dat deze relatie hard (in de broncode) vastgelegd is; althans niet door de programmeurs van die (beide) klassen. Traditioneel gebeurt dat vaak wel, waardoor die klassen moeilijk te hergebruiken zijn.

Soms wordt 'dependency injection' gezien als een bijzondere vorm van 'inversion of control', wat voor het eerst beschreven werd door Martin Fowler.[1]

Gebruik bewerken

Hoewel dit patroon in concept vrij eenvoudig is, vereist het discipline van de ontwikkelaars om het correct uit te voeren. Dit komt ook omdat diverse populaire programmeertalen dit patroon niet goed ondersteunen. Er zijn diverse uitbreidingen, voor diverse talen, om programmeurs te ondersteunen. Spring (voor Java) is waarschijnlijk het bekendste.

Hard koppelen is in de praktijk vaak makkelijker te programmeren, zeker voor kleine programma's waarbij de nadelen (van harde koppelingen) klein zijn. Daardoor is het soms voor beginnende programmeurs lastig om dit 'betere' patroon te leren gebruiken.

Voorbeeld bewerken

Een voorbeeld van losjes koppelen, buiten de computerindustrie, is het verbinden van een (snoer)schakelaar en een lamp. Om het licht aan en uit te kunnen doen, is het noodzakelijk de schakelaar en de lamp te verbinden. In praktijk doet iedereen dat met een stukje snoer. Essentieel hierbij is dat zowel de lamp als de schakelaar eenzelfde (standaard) koppeling hebben: het kroonsteentje. Met een eenvoudige handeling (het vastdraaien van een schroef) komt de verbinding tot stand zonder dat de lamp of de schakelaar veranderd of aangepast hoeft te worden. De koppeling is losjes: noch de lamp, noch de schakelaar zijn speciaal ontworpen om met de ander gebruikt te worden. Ook een andere lamp (van een ander merk of type), meerdere lampen of een of meerdere (andere) schakelaars kunnen gebruikt worden.

Vergelijk dat met een staande lamp met een ingebouwde schakelaar: ook die bedient de lamp maar de koppeling is hard. Het is nauwelijks mogelijk om een andere schakelaar te gebruiken.

Programmeeruitdaging bewerken

Bij het programmeren zien we dezelfde uitdaging: enerzijds zijn er aparte onderdelen (klassen) die moeten samenwerken. Anderzijds willen we die onderdelen onafhankelijk van elkaar ontwikkelen zodat ze op zo veel mogelijk plaatsen gebruikt kunnen worden.

Middels 'dependency injection' is het mogelijk om de onderdelen los van elkaar te ontwikkelen en daarna te verbinden. Er zijn meerdere manieren om dit te bereiken, een programmeur kan hiervoor zelf code schrijven of er kan voor een framework (of tool) gekozen worden.

In alle gevallen zal de programmacode (het object) stukjes code (attributen) bevatten die overeenkomen met het kroonsteentje zoals in het voorbeeld hierboven. Maar ook functies die dat attribuut ook effectief kunnen zetten (het vastdraaien van het schroefje). De rest van de code zal gebruik maken van dat attribuut dat wijst naar een andere object (zoiets als het snoertje in het voorbeeld).

Codevoorbeeld bewerken

Bovenstaand schakelaar-lampvoorbeeld kan vrijwel direct omgezet worden in code. We hebben twee klassen nodig (Lamp, Schakelaar) en één interface of protocol (AanUit). Omdat veel talen dit laatste niet ondersteunen, gebruiken we hiervoor een klasse: IAanuit. In onderstaand voorbeeld is deze overigens verder niet gebruikt.

Onderstaande voorbeeldcode is geschreven in Python. Het kan eenvoudig omgezet worden naar andere programmeertalen.

OMHOOG=1
OMLAAG=2
class IAanUit (object):
   def aan(sender): pass
   def uit(sender): pass

class Lamp(object):                     # Voldoet aan IAanUit
   def __init__(self):
      self.isAan = False
   def aan(self, sender):
      self.isAan = True
      print 'Het licht gaat aan'
   def uit(self, sender):
      self.isAan = False
      print 'Het licht gaat uit'

class Schakelaar(object):
   def __init__(self, outlet):          # outlet is de kern van dependency-injectie -- zie tekst
      self.status = None                # Initieel is de status in (hier) niet bekend
      self.outlet = outlet              # outlet verwijst naar (bijv) de lamp -- outlet moet voldoen aan het IAanUit-protocol
   def omhoog(self):
      if self.status != OMHOOG:
         self.status = OMHOOG
         self.outlet.aan(sender=self)   # Dit roept de aan()-methode aan van self.outlet
   def omlaag(self):
      if self.status != OMLAAG:
         self.status = OMLAAG
         self.outlet.uit(sender=self)   # Hiermee wordt (bijv een lamp) uit gezet

def main():
   l = Lamp()
   s = Schakelaar(l)
   # ...
   s.omhoog()                           # Het licht gaat aan
   s.omlaag()
   s.omlaag()
   
main()

In dit Python-voorbeeld zijn de klassen Lamp en Schakelaar geheel onafhankelijk van elkaar, het enige wat ze delen is het informele IAanUit protocol. De schakelaar verstuurt de berichten en de lamp kan die dan ontvangen en verwerken. Pas in de main routine (bevindt zich meestal in een ander bestand) worden de instanties aan elkaar gekoppeld. In dit eenvoudige voorbeeld gebeurt dit door een instantie (referentie) door te geven bij het aanmaken van het object. In andere voorbeelden gebeurt dit pas later, bijvoorbeeld met een 'setOutlet()'- of 'verbindSchakelaar()'-methode van de klasse Schakelaar.

De kern van de oplossing is het attribuut outlet in de klasse 'Schakelaar'. Dit is een verwijzing (referentie, pointer) naar een ander object. Nadat deze 'outlet' eenmaal gezet is kunnen berichten naar dat object (bijvoorbeeld een lamp) gestuurd worden. Merk echter op dat de (implementatie-code van de) Schakelaar op geen manier weet dat het een lamp is. De schakelaar kan dus ook andere objecten bedienen, zolang die maar voldoen aan het IAanUit-protocol.
Noot: In sommige talen, zoals in Objective-C, kan expliciet gemaakt worden dat het object waar naar verwezen wordt moet voldoen aan het correcte protocol (hier IAanUit), in Python kan dat niet.

Dit voorbeeld geeft ook een subtielere vorm van dependency injection: de zender-parameter van beide methodes in het IAanUit-protocol. De schakelaar geeft bij het bedienen van de lamp zichzelf mee als parameter. Hierdoor 'weet' de ontvangende klasse wat de zender is. En is er dus een koppeling: de ontvanger kan berichten sturen aan de verzender. In bovenstaand voorbeeld gebeurt dat niet, maar het is eenvoudig om dit op te nemen.

Alternatieven bewerken

Referenties bewerken

  1. [1], Martin Fowler is waarschijnlijk de eerste die een vorm van 'dependency injection': Inversion of control (Omgekeerde controle) heeft beschreven.