Dependency Injection
Ik doe een poging om het MVC model, de voor en nadelen, beter te begrijpen. Een van de uitdagingen die ik tegenkwam betrof het injecteren van afhankelijkheden in bijvoorbeeld een specifieke controller en daarbij te blijven voldoen aan het idee van object isolatie (en geen tight coupling).
Zoekende op het internet vond ik uiteenlopende oplossingen en patronen om hiermee om te gaan. Veel van deze voorbeelden vond ik echter bijzonder complex, zeer slecht leesbaar of te abstract (veel werk en code). Daarbij ontstond het idee om hiermee in mijn hobbyapplicatie anders om te gaan.
In mijn huidige implementatie zoekt mijn dispatcher naar specifieke comments (mbv PHP tokenizer) in de te laden controller file en injecteert de gewenste objectreferenties op basis van de daar aangetroffen instructies. Zie onderstaande image.
Ik ben echter benieuwd naar redenen en jullie ervaringen waarom deze aanpak af te raden is.
Ook zou ik graag willen begrijpen waarom veel implementaties uiteindelijk zo complext lijken te worden.
Alvast bedankt voor jullie gedachten.
Grt Donuts,
Gewijzigd op 30/12/2019 12:14:06 door DonutsNL Donut
1) "waarom veel implementaties uiteindelijk zo complext lijken te worden" -> zie bovenstaand voorbeeld.
2a) Persoonlijk heb ik een hekel aan dit soort pseudo-code-in-comments. Als het code is schrijf het dan als code, ipv dat een aparte parse slag, vermoedelijk met wat caching, allerlei "magische dingen" doet.
2b) Eigenlijk heb je hier dus toch gewoon een tight coupling gemaakt?
3) Het is "Dependency injection" (alhoewel: "Depen-dancy" ook wel een grappig - swingend - woord is).
Gewijzigd op 30/12/2019 08:35:53 door Rob Doemaarwat
Aangaande 2a, Implementatie van bovenstaande betreft ongeveer 5 regels code, zonder caching of andere moeilijke dingen. Anders dan een persoonlijke voorkeur lees ik weinig technische argumentatie. Mijn doel met deze impl is gemak en flexibiliteit, hetgeen ook het doel lijkt te zijn van complexere implementaties. De vraag of er technische argumenten zijn deze aanpak wel/niet te gebruiken blijft.
De overige punten zetten me aan tot nadenken over of het corrigeren van :)... waarvoor dank.
:dance:
Gewijzigd op 30/12/2019 12:31:14 door DonutsNL Donut
DonutsNL Donut op 30/12/2019 12:30:41:
al kan ik niet zoveel met de eerste opmerking.
In elk framework zal het in 1e instantie wel "simpel" begonnen zijn, maar zijn er in de loop der tijd steeds meer toeters en bellen bij gekomen. Als je dan halverwege instapt is het vrij complex. Omdat je dit nu zelf ontwikkelt ben je er vanaf T=0 bij (en bouw je alleen die functies in die je nodig hebt), waardoor je het zelf 100% blijft snappen en logisch vindt.
DonutsNL Donut op 30/12/2019 12:30:41:
zonder caching of andere moeilijke dingen
Dus elke keer als je dit bestand "include" (evt. via autoloading) is er ergens een trigger die het nogmaals in PHP tokens omzet, de juiste commentaar-regels er uit vist, en daar nog wat magisch doet? Wat doet dat me je performance? ("normale frameworks" cachen dit soort "annotations" - het liefst een PHP formaat, zodat het nog weer eens door de OPcache wordt gecached).
De kracht van OOP ligt in het feit dat je classes kunt herbruiken. Iedere class moet je zien als een losstaand "ding".
Stel je hebt een class die banden op een auto monteert (ja, je moet wat hè ... het gaat om het voorbeeld), dan wil je dat die ene class banden kan monteren op iedere auto en kan omgaan met alle soorten en maten banden van allerlei merken. Je wil niet dat je voor ieder type band / type merk / afmeting / type auto een aparte class moet schrijven.
Die class moet dus super onafhankelijk zijn. Hij moet nergens aan gebonden zijn, niet aan een bepaald type band, niet aan een bepaalde afmeting, niet aan een merk enz.
Je vroeg: "Ik ben echter benieuwd naar redenen en jullie ervaringen waarom deze aanpak af te raden is."
Jouw aanpak is af te raden omdat je in de class zelf hebt aangegeven welke andere classes er gebruikt moeten worden, en dat wil je niet. De wijze waarop je dat doet (in jouw geval via een comment), doet daarbij niet terzake.
Om even terug te komen op de class die de banden monteert ... in feite zeg jij nu in jouw comment "gebruik altijd sleutel X met maat 8 om een band te monteren". Die sleutel X met maat 8 zit nu dus "vast" aan jouw banden-monteer-class. En dat wil je niet, want wat als je ineens andere banden wilt monteren waarvoor sleutel Q met maat 3 benodigd is? Dan werkt je class ineens niet meer.
Wat je dus wil, is "van buitenaf" de sleutel aangeven aan de banden-monteer-class. Op die manier kun je telkens een andere sleutel meegeven:
Code (php)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
<?php
$sleutel_type = 'Q';
$sleutel_size = '12';
$banden_monteren = new BandenMonteerClass($sleutel_type, $sleutel_size);
$banden_monteren->start();
?>
$sleutel_type = 'Q';
$sleutel_size = '12';
$banden_monteren = new BandenMonteerClass($sleutel_type, $sleutel_size);
$banden_monteren->start();
?>
En vervolgens als je andere banden wilt monteren:
Code (php)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
<?php
$sleutel_type = 'L';
$sleutel_size = '4';
$banden_monteren = new BandenMonteerClass($sleutel_type, $sleutel_size);
$banden_monteren->start();
?>
$sleutel_type = 'L';
$sleutel_size = '4';
$banden_monteren = new BandenMonteerClass($sleutel_type, $sleutel_size);
$banden_monteren->start();
?>
Hoop dat dit het iets duidelijker voor je maakt.
Gewijzigd op 30/12/2019 23:30:05 door Ozzie PHP
Code (php)
Je dependencies zijn in wezen gewoon class constants, alleen heb je de definities verplaatst van echte constanten naar comments. Daar zie ik geen goede reden voor: het is een onlogische omweg.
Scheiding van het gegevensdomein ('Model') komt al met het gebruik van een database. Je zou in PHP kunnen volstaan met het Active Record patroon dat PHP objecten automatisch vult door het uitlezen van structeren in de database. Maar het gegevensdomein reikt bijna altijd verder dan een enkele tabel. Je kunt tabel-oversteigende SQL-queries in Models stoppen via heredoc, nog beter zou zijn om ze als views en functies in de database te schrijven.
Afhankelijk van je perceptie van de functie van een database kan je kiezen voor ORM, waarbij de gegevensstructuur in PHP wordt gedefineerd en de database als slaaf gezien kan worden van je scripts. Dit patroon is nog niet eens zo onlogisch gezien de geschiedenis van MySQL. Maar wanneer je vindt dat de database server de eigenaar is van de gegevens en garanties moet kunnen geven over de consistentie er van, en dat er naast jouw client in PHP nog andere clients zijn die van dezelfde data gebruik maken, is ORM ineens geen logische keuze meer. Laat de database vooral doen waar het goed in is, in ieder geval beter dan PHP. Het komt de systeemprestaties ten goede.
Scheiden van de presentatie ('View') van gegevens is lastiger, omdat er dankzij de browser meerdere abstractielagen bestaan. De structuur van de presentatie is doorgaans in HTML5, de layout in CSS, interactiviteit in JavaScript. Een veelgebruikte manier om hier mee om te gaan is om alles zo goed als het gaat maar in templates te stoppen, met bijbehorende 'widgets'. Maar je kunt er ook voor kiezen om te denken in gebruiksinterface-componenten, en hiervoor voorzieningen te maken zoals PHP-objecten voor formulieren, tabellen, knoppen, die pas in een later stadium worden vertaald naar HTML, CSS en JS.
Dan heb je nog de regelaars ('Controller') voor de 'business-logic', die nog wel eens wordt misbruikt voor allerlei andere code die er eigenlijk niet in thuis hoort. Een oorzaak hiervan is dat veel mensen er in eerste instantie vanuitgaan dat een View niet direct een Model mag bevragen. Maar dit mag juist wél met MVC. Een View hoeft het dus niet te doen, maar het mag wel, waardoor een Controller minder hoeft te doen. Een View mag uitdrukkelijk _niet_ de data van een Model muteren, dat moet altijd via een Controller.
Voor alle objecten die in een framework voor komen wordt gezegd dat het gemakkelijk te testen moet zijn, met als voorwaarde dat objecten zelf geen nieuwe objecten mogen aanmaken. In plaats daarvan moeten alle andere objecten die een object nodig heeft, worden meegegeven ('Dependency injection'). Dat kan via constructors, of methoden. Het is gemakkelijk om de weg kwijt te raken als je een grote hoeveelheid objecten moet beheren. Veelgebruikte frameworks bieden bijvoorbeeld service locators aan, maar dat wordt ook wel weer gezien als een anti-patroon. Dan kan je beter het factory-patroon gebruiken, wat meer werk is om op te zetten maar wat uiteindelijk minder verwerkingstijd kost.
Volgens de stichter van PHP, Rasmus Lerdorf, is het niet goed mogelijk om een raamwerk te maken dat geschikt is voor meerdere soorten applicaties: https://www.youtube.com/watch?v=DuB6UjEsY_Y
De tweede beste optie die dan over lijkt te blijven is om zelf maar een raamwerk te schrijven dat geschikt is voor de eigen applicatie, waarbij je niet moet schromen om goede adviezen en (waar dat mag) code van andere raamwerken over te nemen.