#Utveckling & Arkitektur

Inversion of Control hjälper dig att uppfylla SOLID-principerna, Del 2

Inversion of control genom dependency injection och hur detta hjälper dig att uppfylla SOLID-principerna

I del 1 av den här lilla serien tittade vi på SOLID och hur filosifin Inversion of Control, IoC, hjälper utvecklare och arkitekter att uppnå dessa principer. Nu är det dags att ta ett konkret exempel, med hjälp av: 

Dependency injection

Dependency Injection, DI i kortform, är ett designmönster som uppfyller IoC. I korthet innebär detta att man, när man från en klass A anropar kod i en annan klass B, inte initialiserar ett B-objekt inne i koden i A utan tillför den i ett anrop. Detta kallas att man injicerar B i A och på så sätt även injicerar beroendet av B i A (därav namnet). Själva injektionen kan ske genom

  • Konstruktor-injektion, alltså att B ges som en parameter till A:s konstruktor
  • Property-injektion, alltså att B injiceras till en publik property i A
  • Metod-injektion, att B injiceras som en parameter direkt i den metod i A där B anropas

 
Jag har begränsat mig till varianten konstruktor-injektion. De andra två varianterna fungerar snarlikt men är svårare att implementera och kan även medföra en ökad koppling mellan klasserna om man inte ser upp, därför bör man undvika dem.

Vi kan börja med att dra oss till minnes vår lådpackar-robot från del 1, den här gången i sällskap med en påsfyllar-robot. Vi tänker oss nu att de ska tillverka Bag-in-box-behållare för till exempel äppelmust (som ju är mycket gott!)

 

2_robots.png



Här nedan ges två exempel på ett styrsystem för våra två robotar, där det övre exemplet utformats utan IoC-modifieringar och den undre utformats enligt DI: 

public class PackingRunnerWithNoIoC

    public PackingRunnerWithNoIoC() 

    { 

    } 

 

    public Box FillBagInBox() 

    { 

        var bagFiller = new BagFiller(); 

        var bag = bagFiller.FillBag(); 

        var boxPacker = new BoxPacker(); 

        var box = boxPacker.PackBox(bag); 

        return box; 

    } 

 

public class PackingRunnerUsingDI 

    private IBagFiller _bagFiller; 

    private IBoxPacker _boxPacker; 

 

    public PackingRunnerUsingDI(IBagFiller bagFiller, IBoxPacker boxPacker) 

    { 

        _bagFiller = bagFiller; 

        _boxPacker = boxPacker; 

    } 

 

    public Box FillBagInBox() 

    { 

        var bag = _bagFiller.FillBag(); 

        var box = _boxPacker.PackBox(bag); 

        return box; 

    } 

Skillnaden är som vi ser inte direkt drastisk för kodens utseende, men man inser till exempel problemet i det övre kodexemplet om man vill testa metoden boxPacker.PackBox(bag) utan att köra igång riktiga robotar, till exempel för att de inte är monterade ännu.

 

kontrollpanel.png

 

I PackingRunnerWithNoIoC har man inte alls något inflytande över hur anslutningen till de två packar-robotarna skapas, utan de ansluts automatiskt i konstruktorn som instanser av den klass som är definierad där. PackingRunnerUsingDI däremot injicerar dessa anrop genom interface-parametrar i konstruktorn. Då kan man lätt konstruera fejk-robotar, till exempel genom att koppla in diagnosprogram som implementerar rätt interface. Sedan är det lätt att injicera dem under testet utan risk för att påverka omkringliggande data, funktioner eller äppelskörd:

    public class PackingPlant

    {

        Box RunPacking()

        {

            IBagFiller bagFiller = new BagFiller();

            IBoxPacker boxPacker = new BoxPacker();

            var runner = new PackingRunnerUsingDI(bagFiller, boxPacker);

            return runner.FillBagInBox();

        }

    }

 

    public class PackingTester

    {

        void RunTest()

        {

            IBagFiller bagFiller = new FakeBagFiller();

            IBoxPacker boxPacker = new FakeBoxPacker();

            var runner = new PackingRunnerUsingDI(bagFiller, boxPacker);

            BoxTester.Test(runner.FillBagInBox());

        }

    }

 

Sammanfattning

Det är alltså lätt att se nyttan i dependency injection när man börjar tänka tester och diagnostik. I förlängningen kan man även tänka sig möjligheterna att utnyttja denna teknik till vidareutveckling och refaktorisering.

Det finns dock fortfarande frågetecken som dyker upp, till exempel om man får en anropskedja med fem-sex steg. Vem ska då egentligen initialisera BagFillern och BoxPackern? Detta kommer vi att undersöka i del 3.

Missade du vår del 1 hittar du den här, eller varför inte läsa Johans intressanta bloggpost om refaktorisering.  

Publicerad: 2016-03-10