IoC-containers en afhankelijkheidsinjectie begrijpen

Ik begrijp het:

  • Een afhankelijkheid is wanneer een instantie van KlasseA een instantie van KlasseB nodig heeft om een ​​nieuwe instantie van KlasseA te instantiëren.
  • Een afhankelijkheidsinjectie is wanneer ClassA een instantie van ClassB wordt doorgegeven, hetzij via een parameter in de constructor van ClassA of via een set~DependencyNameHere~(~DependencyNameHere~ $param) functie. (Dit is een van de gebieden waar ik niet helemaal zeker van ben).
  • Een IoC-container is een singleton-klasse (er kan slechts 1 instantie tegelijk worden geïnstantieerd) waarin de specifieke manier om objecten van die klasse voor dit project te instantiëren kan worden geregistreerd. Hier is een link naar een voorbeeld van wat ik probeer te beschrijven met de klassedefinitie voor de IoC-container die ik heb gebruikt

Dus op dit punt begin ik te proberen de IoC-container te gebruiken voor meer gecompliceerde scenario’s. Vanaf nu lijkt het erop dat ik, om de IoC-container te gebruiken, beperkt ben tot een heeft-een-relatie voor vrijwel elke klasse die ik wil maken en die afhankelijkheden heeft die hij in de IoC-container wil definiëren. Wat als ik een klasse wil maken die een klasse erft, maar alleen als de bovenliggende klasse op een specifieke manier is gemaakt en is geregistreerd in de IoC-container.

Dus bijvoorbeeld: ik wil een onderliggende klasse van mysqli maken, maar ik wil deze klasse registreren in de IoC-container om alleen te instantiëren met de bovenliggende klasse die is geconstrueerd op een manier die ik eerder heb geregistreerd in de IoC-container. Ik kan geen manier bedenken om dit te doen zonder code te dupliceren (en aangezien dit een leerproject is, probeer ik het zo ‘puur’ mogelijk te houden). Hier zijn nog enkele voorbeelden van wat ik probeer te beschrijven.

Dus hier zijn enkele van mijn vragen:

  • Is wat ik hierboven probeer te doen mogelijk zonder een of ander principe van OOP te overtreden? Ik weet dat ik in c++ dynamisch geheugen en een kopie-constructor zou kunnen gebruiken om dit te bereiken, maar ik heb dat soort functionaliteit niet kunnen vinden in php. (Ik moet toegeven dat ik heel weinig ervaring heb met het gebruik van een van de andere magische methoden naast __construct, maar door te lezen en __clone als ik het goed heb begrepen, kon ik het in de constructor niet gebruiken om de kindklasse die wordt geïnstantieerd een kloon van een instantie van de bovenliggende klasse).
  • Waar moeten al mijn afhankelijkheidsklassedefinities naartoe gaan met betrekking tot de IoC? (Moet mijn IoC.php gewoon een heleboel required_once(‘dependencyClassDefinition.php’) bovenaan hebben? Mijn instinctieve reactie is dat er een betere manier is, maar ik heb er nog geen bedacht)
  • In welk bestand moet ik mijn objecten registreren? Doet momenteel alle aanroepen naar IoC::register() in het IoC.php-bestand na de klassedefinitie.
  • Moet ik een afhankelijkheid registreren in de IoC voordat ik een klas registreer die die afhankelijkheid nodig heeft? Aangezien ik de anonieme functie pas aanroep als ik een object heb geïnstantieerd dat in de IoC is geregistreerd, vermoed ik van niet, maar het is nog steeds een punt van zorg.
  • Is er nog iets dat ik over het hoofd zie dat ik zou moeten doen of gebruiken? Ik probeer het stap voor stap te doen, maar ik wil ook niet weten dat mijn code herbruikbaar zal zijn en, belangrijker nog, dat iemand die niets over mijn project weet, het kan lezen en begrijpen.

Antwoord 1, autoriteit 100%

Simpel gezegd (omdat het geen probleem is dat beperkt is tot alleen de OOP-wereld), is een afhankelijkheideen situatie waarin component A (afhankelijk van) component B nodig heeft om de dingen te doen die het zou moeten doen. Het woord wordt ook gebruikt om de afhankelijke component in dit scenario te beschrijven. Om dit in OOP/PHP-termen te zeggen, beschouw het volgende voorbeeld met de verplichte auto-analogie:

class Car {
    public function start() {
        $engine = new Engine();
        $engine->vroom();
    }
}

Carafhankelijkvan Engine. Engineis de afhankelijkheidvan Car. Dit stukje code is echter behoorlijk slecht, omdat:

  • de afhankelijkheid is impliciet; je weet niet dat het er is totdat je de code van de Carinspecteert
  • de klassen zijn nauw aan elkaar gekoppeld; je kunt de Engineniet vervangen door MockEnginevoor testdoeleinden of TurboEnginedie de originele uitbreidt zonder de Car.
  • Het ziet er nogal dwaas uit dat een auto zelf een motor kan bouwen, nietwaar?

Afhankelijkheidsinjectieis een manier om al deze problemen op te lossen door het feit dat CarEnginenodig heeft expliciet te maken en het expliciet te voorzien van een:

class Car {
    protected $engine;
    public function __construct(Engine $engine) {
        $this->engine = $engine;
    }
    public function start() {
        $this->engine->vroom();
    }
}
$engine = new SuperDuperTurboEnginePlus(); // a subclass of Engine
$car = new Car($engine);

Het bovenstaande is een voorbeeld van constructor-injectie, waarbij de afhankelijkheid (het afhankelijke object) wordt geleverd aan de afhankelijke (consument) via de klassenconstructor. Een andere manier zou zijn om een ​​setEngine-methode in de klasse Carbloot te leggen en deze te gebruiken om een ​​instantie van Enginete injecteren. Dit staat bekend als setter-injectieen is vooral nuttig voor afhankelijkheden die tijdens runtime zouden moeten worden verwisseld.

Elk niet-triviaal project bestaat uit een aantal onderling afhankelijke componenten en het wordt gemakkelijk om het overzicht kwijt te raken over wat er vrij snel wordt geïnjecteerd. Een dependency injection-containeris een object dat weet hoe andere objecten moeten worden geïnstantieerd en geconfigureerd, weet wat hun relatie is met andere objecten in het project en dat de afhankelijkheidsinjectie voor u doet. Hiermee kunt u het beheer van alle (inter)afhankelijkheden van uw project centraliseren en, nog belangrijker, het mogelijk maken om een ​​of meer ervan te wijzigen/spotten zonder dat u een heleboel plaatsen in uw code hoeft te wijzigen.

Laten we de auto-analogie achterwege laten en als voorbeeld kijken naar wat OP probeert te bereiken. Laten we zeggen dat we een Database-object hebben, afhankelijk van het mysqli-object. Laten we zeggen dat we een echt primitieve afhankelijkheidsindection-containerklasse DICwillen gebruiken die twee methoden blootlegt: register($name, $callback)om een ​​manier te registreren om een ​​object te maken onder de opgegeven naam en resolve($name)om het object van die naam te krijgen. Onze containerconfiguratie zou er ongeveer zo uitzien:

$dic = new DIC();
$dic->register('mysqli', function() {
    return new mysqli('somehost','username','password');
});
$dic->register('database', function() use($dic) {
    return new Database($dic->resolve('mysqli'));
});

Merk op dat we onze container vertellen om een ​​instantie van mysqlivan zichzelfte pakken om een ​​instantie van Databasesamen te stellen. Om vervolgens een Database-instantie te krijgen waarvan de afhankelijkheid automatisch wordt geïnjecteerd, doen we simpelweg:

$database = $dic->resolve('database');

Dat is de essentie ervan. Een wat geavanceerdere maar nog steeds relatief eenvoudige en gemakkelijk te begrijpen PHP DI/IoC-container is Pimple. Raadpleeg de documentatie voor meer voorbeelden.


Over OP’s code en vragen:

  • Gebruik geen statische klasse of een singleton voor uw container (of voor wat dan ook); ze zijn allebei slecht. Bekijk in plaats daarvan Pimple.
  • Beslis of je je mysqliWrapper-klasse wilt uitbreidenmysqlof afhankelijkervan.
  • Door IoCte bellen vanuit mysqliWrapperverwissel je de ene afhankelijkheid voor de andere. Uw objecten mogen de container niet kennen of gebruiken; anders is het geen DIC meer, maar Service Locator (anti)patroon.
  • Je hoeft geen klassebestand requirete hebben voordat je het in de container registreert, aangezien je helemaal niet weet of je een object van die klasse gaat gebruiken. Voer al uw containerconfiguratie op één plek uit. Als u geen autoloader gebruikt, kunt u deze requiregebruiken in de anonieme functie die u bij de container registreert.

Aanvullende bronnen:

Other episodes