Sök…


Anmärkningar

En metod som kan vidtas för att skriva programvara är att skapa beroenden efter behov. Detta är ett ganska intuitivt sätt att skriva ett program och är det sätt som de flesta människor tenderar att lära sig, delvis för att det är lätt att följa. En av problemen med denna strategi är att det kan vara svårt att testa. Överväg en metod som gör någon bearbetning baserat på det aktuella datumet. Metoden kan innehålla någon kod som följande:

if (DateTime.Now.Date > processDate)
{
    // Do some processing
}

Koden är direkt beroende av det aktuella datumet. Den här metoden kan vara svår att testa eftersom det aktuella datumet inte lätt kan manipuleras. En metod för att göra koden mer testbar är att ta bort den direkta referensen till det aktuella datumet och istället tillföra (eller injicera) det aktuella datumet till metoden som gör behandlingen. Denna beroendeinjektion kan göra det mycket enklare att testa aspekter av kod genom att använda testdubblar för att förenkla installationssteget för enhetstestet.

IOC-system

En annan aspekt att ta hänsyn till är beroendetas livslängd; i fallet där klassen själv skapar sina egna beroenden (även känd som invarianter), är den sedan ansvarig för att bortskaffa dem. Dependency Injection inverterar detta (och det är därför vi ofta refererar till ett injektionsbibliotek som ett "Inversion of Control" -system) och betyder att istället för att klassen ska ansvara för att skapa, hantera och rensa upp sina beroenden, en extern agent (i detta fall, IoC-systemet) gör det istället.

Detta gör det mycket enklare att ha beroenden som delas mellan instanser av samma klass; överväga till exempel en tjänst som hämtar data från en HTTP-slutpunkt för en klass att konsumera. Eftersom den här tjänsten är statslös (dvs. den har inget internt tillstånd) behöver vi verkligen bara en enda instans av den här tjänsten under hela vår ansökan. Även om det är möjligt (till exempel genom att använda en statisk klass) att göra detta manuellt, är det mycket enklare att skapa klassen och berätta för IoC-systemet att det ska skapas som en Singleton , där endast en instans av klassen finns.

Ett annat exempel är databasförhållanden som används i en webbapplikation, varigenom en ny kontext krävs per begäran (eller tråd) och inte per instans av en controller; detta gör att kontexten kan injiceras i varje lager som utförs av den tråden, utan att behöva manuellt föras runt.

Detta frigör de konsumtiva klasserna från att behöva hantera beroenden.

Constructor Injection

Constructor-injektion är det säkraste sättet att injicera beroenden som en hel klass beror på. Sådana beroenden benämns ofta invarianter , eftersom en instans av klassen inte kan skapas utan att tillhandahålla dem. Genom att kräva att beroendet ska injiceras vid konstruktionen garanteras det att ett objekt inte kan skapas i ett inkonsekvent tillstånd.

Tänk på en klass som måste skriva till en loggfil i felförhållanden. Det är beroende av en ILogger , som kan injiceras och användas vid behov.

public class RecordProcessor
{
    readonly private ILogger _logger;

    public RecordProcessor(ILogger logger)
    {
        _logger = logger;
    }

    public void DoSomeProcessing() {
        // ...
        _logger.Log("Complete");
    }
}

Ibland när du skriver tester kan du notera att konstruktören kräver mer beroenden än vad det faktiskt behövs för ett fall som testas. Ju fler sådana test du har desto mer sannolikt är det att din klass bryter med Single Responsibility Principle (SRP). Det är därför det inte är mycket bra praxis att definiera standardbeteendet för alla hårav med injicerade beroenden i testklassens initialiseringsfas eftersom det kan maskera den potentiella varningssignalen.

Det ojämnaste för detta skulle se ut enligt följande:

[Test]
public void RecordProcessor_DependencyInjectionExample()
{
    ILogger logger = new FakeLoggerImpl(); //or create a mock by a mocking Framework
    
    var sut = new RecordProcessor(logger); //initialize with fake impl in testcode

    Assert.IsTrue(logger.HasCalledExpectedMethod());
}

Fastighetsinsprutning

Genom injektion av egendom kan en klassberoenden uppdateras efter att den har skapats. Detta kan vara användbart om du vill förenkla skapandet av objekt, men ändå låta beroenden åsidosättas av dina test med testdubblar.

Tänk på en klass som måste skriva till en loggfil i feltillstånd. Klassen vet hur man konstruerar en standard Logger , men gör det möjligt att åsidosättas genom egendom injektion. Det är emellertid värt att notera att genom att använda egendomsinjektion på detta sätt kopplar du denna klass tätt med en exakt implementering av ILogger som är ConcreteLogger i det här exemplet. En möjlig lösning kan vara en fabrik som returnerar det nödvändiga ILogger-implementeringen.

public class RecordProcessor
{
    public RecordProcessor()
    {
        Logger = new ConcreteLogger();
    }

    public ILogger Logger { get; set; }

    public void DoSomeProcessing()
    {
        // ...
        _logger.Log("Complete");
    }
}

I de flesta fall är Constructor Injection att föredra framför Property Injection eftersom det ger bättre garantier för objektets tillstånd omedelbart efter dess konstruktion.

Metodinjektion

Metodinjektion är ett finkornigt sätt att injicera beroenden i bearbetning. Överväg en metod som gör någon bearbetning baserat på det aktuella datumet. Det aktuella datumet är svårt att ändra från ett test, så det är mycket lättare att skicka ett datum till den metod som du vill testa.

public void ProcessRecords(DateTime currentDate)
{
    foreach(var record in _records) 
    {
        if (currentDate.Date > record.ProcessDate)
        {
            // Do some processing
        }
    }
}

Behållare / DI-ramverk

Medan extrahering av beroenden från din kod så att de kan injiceras gör din kod lättare att testa, driver det problemet ytterligare upp i hierarkin och kan också resultera i objekt som är svåra att konstruera. Olika ramar för beroendeinsprutning / Inversion of Control Containers har skrivits för att hjälpa till att lösa problemet. Dessa tillåter att mappningar av typ registreras. Dessa registreringar används sedan för att lösa beroenden när behållaren uppmanas att konstruera ett objekt.

Tänk på dessa klasser:

public interface ILogger {
    void Log(string message);
}

public class ConcreteLogger : ILogger
{
    public ConcreteLogger()
    {
        // ...
    }
    public void Log(string message)
    {
        // ...
    }
}
public class SimpleClass
{
    public SimpleClass()
    {
        // ...
    }
}

public class SomeProcessor
{
    public SomeProcessor(ILogger logger, SimpleClass simpleClass)
    {
        // ...
    }
}

För att konstruera SomeProcessor både en instans av ILogger och SimpleClass . En behållare som Unity hjälper till att automatisera denna process.

Först måste behållaren konstrueras och sedan registreras kartläggningar med den. Detta görs vanligtvis bara en gång i en applikation. Området för systemet där detta inträffar är allmänt känt som Composition Root

// Register the container
var container = new UnityContainer();

// Register a type mapping.  This allows a `SimpleClass` instance
// to be constructed whenever it is required.
container.RegisterType<SimpleClass, SimpleClass>();

// Register an instance.  This will use this instance of `ConcreteLogger` 
// Whenever an `ILogger` is required.
container.RegisterInstance<ILogger>(new ConcreteLogger());

När behållaren har konfigurerats kan den användas för att skapa objekt och automatiskt lösa beroenden efter behov:

var processor = container.Resolve<SomeProcessor>();


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow