Dependency Injection

Tags:    diverse
Skrevet af Bruger #4522 @ 18.09.2008

Introduktion


Dependency Injection (fremover kaldt DI) er et programmerings koncept der har ligget og luret i undergrunden, især hos XP-programmører, men som de seneste par år er blevet formaliseret, og kommet op og blevet mainstream. Jeg ønsker i denne korte artikel at kigge på DI, og forklare lidt om hvorfor og hvordan det kan hjælpe programmørere med at udvikle robust og testbar kode.

I sin essens er DI ikke revolutionerende, og præsenterer ikke nogle ideer som vi ikke kendte til før. DI handler mest om at tilegne sig nogle gode vaner når vi snakker om afhængighed kodekomponenter i mellem.

Helt grundlæggende er ideen med DI at minimere ens kodekomponenters afhængighed af andre kodekomponenter (med kodekomponenter mener vi typisk klasser). Så lad os starte med at se på denne afhængighed.

Kode afhængighed


På engelsk kaldes det "coupling" når to kodekomponenter er afhængige af hinanden. DI går ud på at minimere denne afhængighed. Men det er selvfølgelig ikke al "coupling" som kan - eller skal - minimeres.

Du kan ikke udvikle et eneste program uden kode afhængighed. Du har "coupling" blot en klasse kræver en anden klasse for at kunne fungere. Hvis f.eks. du i din klasse har et datamedlem af typen string, har du en "coupling" mellem din klasse og string-klassen. Sådan en afhængighed er helt i orden. Det vil være helt overkill at begynde at introducere interfaces (IText?) i sådanne tilfælde for at gardere sig imod ændringer af string-klassen.

Den type afhængighed vi ønsker at minimere er afhængighed af eksterne komponenter såsom en database, web service, cache etc. Vi ønsker også at minimere afhængighed af kode der kræver meget opsætning (igen database komponenter).

Fordelen ved at minimere denne afhængighed er fleksibilitet samt at det bliver nemmere at vedligeholde koden, men den største fordel (og grunden til at DI opstod som fænomen) er at det gør din kode mere testvenlig med unit tests.

Dependency Injection


DI er et kodemønster vi kan bruge til at minimere afhængighed kodekomponenter imellem. Ideen ganske simpel. I stedet for at bygge en klasse op med indbyggede afhængighed på eksterne komponenter, injiceres de ved køretid (f.eks. kan man så ved unit test injicere et såkaldt mock object ved test af database kode [hvis læseren ikke er bekendt med unit tests og/eller mock objekter anbefales det at læse op på Junit hvis du er Java programmør eller NUnit hvis du er .NET programmør]).

Lad os kigge på et lille eksempel, og lad os starte med ikke at bruge DI. Lad os sige vi har et interface for data tilgang. Det kan se sådanne ud:

Fold kodeboks ind/udKode 


Vi kan så have forskellige implementationer af dette interface, vi kan f.eks. have en klasse der hedder EmployeeOracleDbAccess, og en der hedder EmployeeMySqlDbAccess. Den første klasse kan håndtere at persistere og opdatere employee information fra en Oracle database, og den anden fra en MySql database. For unit test kan vi også have en implementation f.eks. EmployeeMockDbAccess.

Vores Employee klasse kan så se sådanne her ud:

Fold kodeboks ind/udKode 


Problemet med denne kode er at vi har en hård kobling mellem vores Employee klasse og en af vores datatilgangs klasser, her EmployeeOracleDbAccess, og på grund af denne kobling er det faktisk helt unødvendigt at bruge interfaces til datatilgang. På den måde vi har kodet vores klasse er der ingen fordel ved at have benyttet interfaces i modsætning til almindelige klasser. Derudover kan vi nu ikke lave en unit test af vores klasse (i hvert fald ikke uden en masse problemer relateret til databasetilgangen).

Løsningen er her at bruge DI. I stedet for at lave en hård kobling mellem vores Employee klasse og databasetilgangen, injicerer vi denne afhængighed ved køretid.

Injicering af kodekomponenter (typisk en klasse reference) kan gøres på en af to måder (eller begge). Den første måde er blot at injicere afhængigheden via klassens konstruktør.

Lad os se hvordan vores Employee klasse ser ud hvis vi benytter DI.

Fold kodeboks ind/udKode 


Læg mærke til at hvis man benytter default konstruktøren anvendes et default data acceess objekt, men at vi også kan injicere et data object ved køretid ved at bruge den anden konstruktør. På den måde kan vi f.eks. injicere en EmployeeMockDbAccess som vi kan bruge til unit test. Det virker måske enormt simpelt, og det er det sådan set også, men hvis man på denne måde fjerner enhver hård kobling til eksterne komponenter (som f.eks. en database) bliver koden meget mere testbar.

En anden måde hvorpå man kan injicere kode afhængighed er ved at bruge en property (i C# findes en property som et syntakselement, og i Java og C++ vil man i stedet bruge de såkaldte getter/setter metoder).

Om man anvender DI ved at bruge konstruktører eller properties er udelukkende et spørgsmål om personlig præference. Man kan også bruge begge hvis man synes.

DI via frameworks


For mindre projekter er manuel DI, som er det vi har kigget på indtil videre, en god og fleksibel løsning. I store projekter med masser af kode, kan det dog hurtigt blive overvældende med alle de konstruktør overlæssninger (eller ekstra properties) man skal skrive. For at lette programmørens arbejde findes der diverse frameworks man kan bruge til at automatisere brugen af DI. Typisk virker de ved at man i en konfigurationsfil angiver den ønskede DI, og så behøver man ikke skrive noget ekstra kode derudover.

Der findes en del DI frameworks, men en af de populæreste er Spring (til Java) og Spring.NET (til .NET). Spring er et framework med mange flere elementer end kun DI, og er bestemt et kig værd hvis du udvikler større systemer. Et andet meget anvendt framework til .NET er StructureMap.

Afslutning


Vi har i denne artikel kort kigget på hvad DI er, hvorfor det er en god idé at anvende det, samt set på et lille eksempel der anvendte DI. Med DI minimerer vi koblingen mellem vores klasser, hvilket især er godt i tilfælde med eksterne komponenter som database, cache, etc. DI gør det nemmere for os at vedligeholde, og ikke mindst at teste vores kode, da vi altid ved køretid kan injicere de krævede komponenter. DI kan anvendes ved at man enten selv manuelt indbygger det i ens klasser, eller man kan bruge én af de mange DI frameworks (hvilket især er en god idé på større projekter).


Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (1)

User
Bruger #2730 @ 19.09.08 14:49
Rigtig god artikel omkring et interessant emne. Det eneste der irriterer mig lidt er at jeg i Employee klassen er nødt til at vælge hvilken persisterings type der skal benyttes (fx. oracle) det kan give lidt problemer hvis jeg køre på flere forskellige datakilder med samme objekt. Jeg vil nødigt skulle compile en version til Oracle, en version til SQL Server osv. Kunne man evt forestille sig at man kunne opnå persisteringen via nedarvning fra et base objekt, der kunne lave reflection på objektet og derigennem gemme det i databasen, dette objekt kunne så konfigureres til hvilken datakilde der skal benyttes...
Du skal være logget ind for at skrive en kommentar.
t