Dependy Injection och IoC – vad handlar det om?

Dependency Injection

Många erfarna programmerare talar om Dependency Injection och framställer fenomenet som om det vore någon form av hjärnkirurgi. Men, vad handlar det om egentligen?

Man kan kalla Dependency Injection för många saker. Till syvende och sist handlar det inte om annat än att utnyttja de möjligheter som redan erbjuds i programmeringsspråket. Låt oss betrakta följande kodsnutt:

    public class MyClass
    {
        private DataTableReader _reader;

        public MyClass()
        {
            _reader = new DataTableReader(new DataTable());
        }

        private void DoStuffWithTheReader()
        {
            while (_reader.Read())
            {
                //här använder vi _reader-variabeln
            }
        }
    }
Kodexempel 1 – klass som skapar ett DataTableReader-objekt själv

Lägg märke till _reader-variabeln eftersom just den visar på ett beroende, dependency. Variabeln måste alltså skapas för att vi ska kunna använda den senare. Till på köpet är skapandet ytterligare en sak som vi måste göra i konstruktorn, men inte nog med det. Vi måste i praktiken även populera DataTable-objektet. Det är ganska mycket arbete att utföra på konstruktornivå och hur vore det egentligen om vi lät en anropande klass (som alltså använder MyClass) skicka med nödvändiga parametrar i en ny konstruktor? En som tar in en instans av DataTableReader?

public class MyClass
{
    private DataTableReader _reader;
    public MyClass(DataTableReader dataTableReader)
    {
        _reader = dataTableReader;
    }
    private void DoStuffWithTheReader()
    {
        while (_reader.Read())
        {
            //använd _reader-objektet på något sätt
        }
    }
}
Kodexempel 2- klass som tar in ett DataTableReader-objekt via konstruktorn

Sådärja, nu ser det bättre ut! Nu måste vår klass inte ta hand om skapande av en ny DataTableReader utan ansvaret för detta läggs på anropande klassen. I all sin väsentlighet är det detta som dependency injection handlar om. Inte särskilt komplicerat eller hur? Vi har gjort konstruktionen av klassen lite enklare samtidigt som vi inte behöver göra komplicerade fejkkonstruktioner (mocking) vid enhetstester. Ett fejkat DataTableReader-objekt kan vi ju dessutom förse med de egenskaper som vi önskar.

En annan aspekt är att om vi exempelvis får data som ser skum ut i vår metod DoStuffWithTheReader(), kommer vi inte att behöva leta igenom klassen för att se varifrån den kommer. Det enda som vi behöver kika på är metoden som skapar den här klassen (this) och skickar in en instans av DataTableReader.

IoC – Inversion of control

Om vi tittar på föregående exempel så ser vi att vi trots allt har inverterat kontrollen lite grand genom att använda oss av dependency injection. Vi kan dock dra det ytterligare ett strå längre eftersom vår klass, MyClass, fortfarande bestämmer vilket konkret objekt som ska tas som inparameter i konstruktorn. Betänk då om vi skulle vilja använda en annan typ av läsare, t ex SqlDataReader eller OleDbDataReader, då skulle vi gensast stöta på patrull. Givetvis skulle man kunna skapa tre nya klasser som var för sig implementerade en ny konstruktor med inparameter av önskad typ av läsare, men det skulle vara en ganska dålig idé. Det skulle bara sluta med redundant kod, att vi hade spritt ut mer eller mindre identisk kod på flera ställen. Bättre då att låta konstruktorn ta in interface för en inparameterinstans.

Jämför med kodexemplet , Kodexempel 2, ovan. Om vi istället för att skicka med (DataTableReader dataTableReader) rumsterar vi om så att kontruktorn tar in en instans som implementerar interfacet IDataReader får vi en betydligt mera generell klass:

public class MyClass
{
    private IDataReader _reader;
 
    public MyClass(IDataReader dataTableReader)
    {
        _reader = dataTableReader;
    }
    private void DoStuffWithTheReader()
    {
        while (_reader.Read())
        {
            //gör någonting med _reader-objektet
        }
    }
}
Kodexempel 3 – klass som i sin efterfrågar ett godtyckligt objekt som implementerar DataTableReader-objekt själv

Nu behöver inte vår klass ta hand om skapande av en DataTableReader, den ”bryr” sig dessutom inte överhuvudtaget om ifall den får in en instans av ett DataTableReader-objekt alls. Det enda viktiga är att instansens som tas in implementerar interfacet IDataReader (har de metoder/properties som interfacet specificerar). Detta kallas Inversion of Control. Ofta förvirras utvecklare av hjälpmedel för Inversion of Control respektive Dependency Injection (som t ex Ninject). Det är inte nödvändigt att använda dylika hjälpmedel för tekniken, det bara underlättar.

Link to original article.

Annonser