Zoeken…


Invoering

Dependency Injection (DI) is een mooie term voor "dingen doorgeven" . Het enige dat het echt betekent is het doorgeven van de afhankelijkheden van een object via de constructor en / of setters in plaats van ze te creëren bij het maken van objecten in het object. Dependency Injection kan ook verwijzen naar Dependency Injection Containers die de constructie en injectie automatiseren.

Constructor injectie

Objecten zijn vaak afhankelijk van andere objecten. In plaats van de afhankelijkheid in de constructor te maken, moet de afhankelijkheid als parameter in de constructor worden doorgegeven. Dit zorgt ervoor dat er geen nauwe koppeling tussen de objecten is en maakt het mogelijk om de afhankelijkheid van klasse-instantiatie te wijzigen. Dit heeft een aantal voordelen, waaronder het gemakkelijker leesbaar maken van code door de afhankelijkheden expliciet te maken, en het testen eenvoudiger maken omdat de afhankelijkheden gemakkelijker kunnen worden omgeschakeld en bespot.

In het volgende voorbeeld is Component afhankelijk van een exemplaar van Logger , maar er wordt er geen gemaakt. In plaats daarvan moet er een als argument aan de constructor worden doorgegeven.

interface Logger {
    public function log(string $message);
}

class Component {
    private $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }
}

Zonder injectie van afhankelijkheid zou de code waarschijnlijk lijken op:

class Component {
    private $logger;

    public function __construct() {
        $this->logger = new FooLogger();
    }
}

new gebruiken om nieuwe objecten in de constructor te maken, geeft aan dat afhankelijkheidsinjectie niet is gebruikt (of onvolledig is gebruikt) en dat de code nauw is gekoppeld. Het is ook een teken dat de code onvolledig is getest of broze tests bevat die onjuiste veronderstellingen maken over de programmastatus.

In het bovenstaande voorbeeld, waar we in plaats daarvan afhankelijkheidsinjectie gebruiken, kunnen we gemakkelijk overschakelen naar een andere logger als dit nodig wordt. We kunnen bijvoorbeeld een Logger-implementatie gebruiken die zich aanmeldt bij een andere locatie, of die een ander logboekformaat gebruikt, of die zich aanmeldt bij de database in plaats van bij een bestand.

Zetter injectie

Afhankelijkheden kunnen ook worden geïnjecteerd door setters.

interface Logger {
    public function log($message);
}

class Component {
    private $logger;
    private $databaseConnection;

    public function __construct(DatabaseConnection $databaseConnection) {
        $this->databaseConnection = $databaseConnection;
    }

    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }

    public function core() {
        $this->logSave();    
        return $this->databaseConnection->save($this);
    }

    public function logSave() {
         if ($this->logger) {
            $this->logger->log('saving');
        }
    }
}

Dit is vooral interessant wanneer de kernfunctionaliteit van de klasse niet afhankelijk is van de afhankelijkheid om te werken.

Hier is de enige benodigde afhankelijkheid de DatabaseConnection dus deze bevindt zich in de constructor. De afhankelijkheid van Logger is optioneel en hoeft dus geen deel uit te maken van de constructor, waardoor de klasse gemakkelijker te gebruiken is.

Merk op dat het bij het gebruik van setterinjectie beter is om de functionaliteit uit te breiden in plaats van deze te vervangen. Bij het instellen van een afhankelijkheid is er niets dat bevestigt dat de afhankelijkheid op een bepaald punt niet zal veranderen, wat kan leiden tot onverwachte resultaten. Een FileLogger kan bijvoorbeeld eerst worden ingesteld en vervolgens een MailLogger . Dit breekt inkapseling en maakt logboeken moeilijk te vinden, omdat we de afhankelijkheid vervangen .

Om dit te voorkomen, moeten we een afhankelijkheid met setter injectie toe te voegen, als volgt:

interface Logger {
    public function log($message);
}

class Component {
    private $loggers = array();
    private $databaseConnection;

    public function __construct(DatabaseConnection $databaseConnection) {
        $this->databaseConnection = $databaseConnection;
    }

    public function addLogger(Logger $logger) {
        $this->loggers[] = $logger;
    }

    public function core() {
        $this->logSave();
        return $this->databaseConnection->save($this);
    }

    public function logSave() {
        foreach ($this->loggers as $logger) {
            $logger->log('saving');
        }
    }
}

Op deze manier, wanneer we de kernfunctionaliteit gebruiken, zal deze niet breken, zelfs als er geen loggerafhankelijkheid is toegevoegd, en elke logger zal worden gebruikt, hoewel een andere logger had kunnen worden toegevoegd. We breiden de functionaliteit uit in plaats van deze te vervangen .

Container injectie

Dependency Injection (DI) in de context van het gebruik van een Dependency Injection Container (DIC) kan worden gezien als een superset van constructorinjectie. Een DIC analyseert doorgaans de typehints van een klasseconstructeur en lost zijn behoeften op, waarbij de afhankelijkheden die nodig zijn voor de uitvoering van de instantie effectief worden geïnjecteerd.

De exacte implementatie gaat veel verder dan het bereik van dit document, maar in wezen berust een DIC op het gebruik van de handtekening van een klasse ...

namespace Documentation;

class Example
{
    private $meaning;

    public function __construct(Meaning $meaning)
    {
        $this->meaning = $meaning;
    }
}

... om het automatisch te instantiëren, meestal afhankelijk van een automatisch laadsysteem .

// older PHP versions
$container->make('Documentation\Example');

// since PHP 5.5
$container->make(\Documentation\Example::class);

Als je PHP in versie 5.5 gebruikt en een naam van een klasse wilt krijgen op een manier die hierboven wordt getoond, is de tweede manier de juiste manier. Op die manier kun je snel gebruik van de klas vinden met behulp van moderne IDE's, die je enorm zullen helpen met potentiële refactoring. U wilt niet vertrouwen op normale tekenreeksen.

In dit geval weet de Documentation\Example dat het een Meaning heeft en een DIC zou op zijn beurt een Meaning instantiëren. De concrete implementatie hoeft niet afhankelijk te zijn van de consumerende instantie.

In plaats daarvan stellen we regels in de container in, voorafgaand aan het maken van objecten, die instrueert hoe specifieke types indien nodig moeten worden geïnstantieerd.

Dit heeft een aantal voordelen, zoals een DIC kan

  • Deel gemeenschappelijke instanties
  • Lever een fabriek om een typeaanduiding op te lossen
  • Een interfacehandtekening oplossen

Als we regels definiëren over hoe een specifiek type moet worden beheerd, kunnen we nauwkeurige controle krijgen over welke typen worden gedeeld, geïnstantieerd of gemaakt vanuit een fabriek.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow