Van procedureel naar object georiënteerd PHP

Deze tutorial is geïnspireerd door een toespraak van Robert C. Martin die ik een jaar geleden heb bekeken. Het belangrijkste onderwerp van zijn lezing gaat over de mogelijkheid om de laatste programmeertaal te kiezen. Hij behandelt onderwerpen zoals waarom zou een dergelijke taal bestaan? En hoe het eruit zou moeten zien? Maar als je tussen de regels door leest, was er een ander interessant idee dat mijn aandacht trok: de beperkingen die elk programmeerparadigma ons oplegt programmeurs. Dus voordat we ingaan op hoe we een procedureel gebaseerde PHP-app kunnen omzetten in een objectgerichte, wil ik vooraf een klein beetje theorie behandelen.


Paradigma Beperkingen

Elk programmeringsparadigma beperkt ons vermogen om te doen wat we willen doen. Elk van hen neemt iets weg en biedt een alternatief om hetzelfde resultaat te bereiken. Modulair programmeren neemt de onbeperkte programmagrootte weg. Het dwingt de programmeur om modules van maximaal formaat te gebruiken en elke module eindigt met een "go-to" -instructie naar een andere module. Dus de eerste beperking is op grootte. Vervolgens neemt gestructureerd programmeren en procedureel programmeren de "go-to" -instructie weg en beperkt het programmeur tot sequentie, selectie en iteratie. Sequenties zijn variabele toewijzingen, selecties zijn if-else-beslissingen en iteraties zijn do-while-loops. Dit zijn de bouwstenen van de meeste programmeertalen en paradigma's van vandaag.

Objectgeoriënteerd programmeren neemt wegwijzers weg naar functies en introduceert polymorfisme. PHP gebruikt pointers niet op een manier die C wel doet, maar een variant van deze verwijzingen naar functies is te zien in Variabele functies. Hierdoor kan een programmeur de waarde van een variabele gebruiken als de naam van een functie, zodat zoiets als dit kan worden bereikt:

function foo () echo "Dit is foo";  functiebalk ($ param) echo "Dit is balk zegt: $ param";  $ function = 'foo'; $ Function (); // Gaat naar foo () $ function = 'bar'; $ Functie ( 'test'); // Gaat naar bar ()

Dit ziet er op het eerste gezicht misschien niet zo belangrijk uit. Maar bedenk wat we kunnen bereiken met zo'n krachtig hulpmiddel. We kunnen een variabele als een parameter naar een functie sturen en die functie de andere laten oproepen, waarnaar wordt verwezen door de waarde van de parameter. Dit is geweldig. Het stelt ons in staat om de functionaliteit van een functie te veranderen zonder dat hij het weet. Zonder dat de functie zelfs maar enig verschil bemerkt.

We kunnen ook polymorfe oproepen doen met deze techniek.

Denk in plaats van na te denken over wat pointers met functies bieden, na over hoe ze werken. Zijn dit niet alleen verborgen "go-to" -verklaringen? Eigenlijk zijn ze dat, of lijken ze in ieder geval sterk op indirecte "go-to's". Dat is niet erg goed. Wat we hier hebben is eigenlijk een slimme manier om "go-to" te doen zonder het direct te gebruiken. Ik moet toegeven dat het in PHP, zoals het bovenstaande voorbeeld laat zien, vrij gemakkelijk te begrijpen is, maar het kan verwarrend worden bij grotere projecten en veel verschillende functies die van de ene naar de andere functie worden doorgegeven. In C is het nog duisterder en vreselijk moeilijk te begrijpen.

Het is echter niet genoeg om aanwijzingen naar functies weg te halen. Objectgeoriënteerd programmeren moet een vervanging bieden en dat doet het op een elegante manier. Het biedt polymorfisme met een eenvoudige syntaxis. En met polymorfisme komt de grootste waarde objectgeoriënteerde programmeeraanbiedingen: de stroom van controle is tegengesteld aan de broncodeafhankelijkheid.


In de bovenstaande afbeelding illustreerden we een eenvoudig voorbeeld van hoe polymorfe aanroepen plaatsvinden in de twee verschillende paradigma's. Bij procedurele of structurele programmering is de controlestroom vergelijkbaar met de afhankelijkheid van de broncode. Ze wijzen allebei op de meer concrete implementatie van het printgedrag.

Bij objectgeoriënteerd programmeren kunnen we de afhankelijkheid van de broncode omkeren en naar de meer abstracte implementatie wijzen, terwijl de controlestroom naar de meer concrete implementatie blijft wijzen. Dit is essentieel, want we willen dat onze controle het meest concrete en vluchtige gedeelte van onze code bereikt en dat we ons resultaat precies zo krijgen als we het willen, maar in onze broncode willen we precies het tegenovergestelde. In onze broncode willen we dat de concrete en vluchtige dingen uit de weg blijven, gemakkelijk te veranderen zijn en zo min mogelijk van de rest van onze code beïnvloeden. Laat de vluchtige delen vaak veranderen, maar houd de meer abstracte delen ongewijzigd. Je kunt meer lezen over het principe van de afhankelijkheid van inversie in het originele onderzoekspaper geschreven door Robert C. Martin.


De huidige opdracht

In dit hoofdstuk maken we een eenvoudige applicatie om Google-kalenders en de gebeurtenissen daarin weer te geven. Eerst zullen we een procedurele aanpak volgen, alleen eenvoudige functies gebruiken en elke vorm van klassen of objecten vermijden. Met de toepassing kunt u uw Google-agenda's en -evenementen weergeven. Vervolgens gaan we het probleem een ​​stap verder door onze procedurele code te behouden en te beginnen met het organiseren van gedrag. Uiteindelijk zullen we het omzetten in een object-georiënteerde versie.


PHP Google API Client

Google biedt een API-client voor PHP. We zullen het gebruiken om verbinding te maken met ons Google-account, zodat we daar kalenders kunnen bewerken. Als u de code wilt uitvoeren, moet u uw Google-account instellen om kalenderquery's te accepteren.

Hoewel dit een vereiste is voor de tutorial, is dit niet het hoofdonderwerp. Dus in plaats van dat ik de stappen die u moet nemen herhaalt, zal ik u naar de juiste documentatie verwijzen. Maak je geen zorgen, het is heel eenvoudig in te stellen en het duurt slechts ongeveer vijf minuten.

De PHP Google API-clientcode is in elk project opgenomen in de voorbeeldcode die aan deze zelfstudie is gekoppeld. Ik raad aan dat je die gebruikt. Als alternatief, als je benieuwd bent naar hoe je het zelf kunt installeren, bekijk dan de officiële documentatie.

Volg daarna de instructies en vul de informatie in de apiAccess.php het dossier. Dit bestand is vereist voor zowel procedurele als objectgeoriënteerde voorbeelden, dus u hoeft het niet te herhalen. Ik heb mijn sleutels daar achtergelaten, zodat je de jouwe gemakkelijker kunt identificeren en invullen.

Als u NetBeans toevallig zou gebruiken, liet ik de projectbestanden in de mappen met de verschillende voorbeelden. Op deze manier kunt u de projecten eenvoudig openen en direct uitvoeren op een lokale PHP-server (PHP 5.4 is vereist) door eenvoudigweg te selecteren Run / Run Project.

De clientbibliotheek die moet worden verbonden met de Google API is objectgericht. In het belang van ons functionele voorbeeld heb ik een kleine set functies geschreven die erin passen, de functionaliteiten die we nodig hebben. Op deze manier kunnen we een procedurelaag gebruiken die is geschreven over de objectgeoriënteerde clientbibliotheek, zodat onze code geen objecten hoeft te gebruiken.

Als u snel wilt testen of uw code en verbinding met de Google API werken, gebruikt u gewoon de onderstaande code als uw index.php het dossier. Het zou alle kalenders moeten vermelden die u in uw account hebt. Er zou minstens één kalender moeten zijn met de samenvatting veld dat jouw naam is. Als u een agenda heeft met de verjaardagen van uw contactpersoon, werkt die mogelijk niet met deze Google API, maar raak niet in paniek, kies gewoon een andere.

require_once './google-api-php-client/src/Google_Client.php'; require_once './google-api-php-client/src/contrib/Google_CalendarService.php'; require_once __DIR__. '/ ... /apiAccess.php'; require_once './functins_google_api.php'; require_once './functions.php'; session_start (); $ client = createClient (); als (! authenticate ($ client)) terugkomt; listAllCalendars ($ client);

Deze index.php bestand zal het toegangspunt tot onze applicatie zijn. We zullen geen web framework gebruiken of iets bijzonders. We zullen gewoon wat HTML-code uitvoeren.


Een directe procedurele aanpak

Nu we weten wat we aan het bouwen zijn en wat we gaan gebruiken, ga je gang en download je de bijgevoegde broncode. Ik zal er fragmenten van voorzien, maar om het geheel te kunnen zien, wil je toegang hebben tot de originele bron.

Voor deze aanpak willen we de zaken gewoon laten werken. Onze code zal op een zeer rudimentaire manier worden georganiseerd, met slechts een paar bestanden, zoals deze:

  • index.php - het enige bestand dat we rechtstreeks vanuit de browser benaderen en doorgeven aan GET-parameters.
  • functions_google_api.php - de wrapper over de Google API waarover we het hierboven gehad hebben.
  • functions.php - waar alles gebeurt.

functions.php zal alles bevatten wat onze applicatie doet. Zowel de routeringslogica, de presentaties en welke waarden en gedrag dan ook daarin begraven kunnen worden. Deze applicatie is vrij eenvoudig, de hoofdlogica is als volgt.


We hebben een enkele functie genaamd doUserAction (), die beslist met een lange if-else verklaring, welke andere methoden om te bellen op basis van de parameters in de KRIJGEN variabel. De methoden maken vervolgens verbinding met de Google-agenda met behulp van de API en printen naar het scherm, wat we ook willen aanvragen.

function printCalendarContents ($ client) putTitle ('Dit zijn jullie evenementen voor'. getCalendar ($ client, $ _GET ['showThisCalendar']) ['summary']. 'calendar:'); foreach (retrievEvents ($ client, $ _GET ['showThisCalendar']) als $ event) print ('
'. date ('Y-m-d H: m', strtotime ($ event ['created']))); putLink ('? showThisEvent ='. htmlentities ($ event ['id']). '& calendarId ='. htmlentities ($ _ GET ['showThisCalendar']), $ event ['summary']); afdrukken('
'); afdrukken('
');

Dit voorbeeld is waarschijnlijk de meest gecompliceerde functie in onze code. Het roept een genoemde helperfunctie aan putTitle (), die gewoon wat opgemaakte HTML voor de kop afdrukt. De titel zal de naam voor onze kalender bevatten die kan worden verkregen door te bellen getCalendar () van functions_google_api.php. De geretourneerde agenda is een array met een samenvatting veld. Dat is wat we zoeken.

De $ client variabele wordt overal in al onze functies doorgegeven. Het is vereist om verbinding te maken met de Google API. We zullen dit later behandelen.

Vervolgens gaan we over alle gebeurtenissen in de huidige kalender. Deze lijst met arrays wordt verkregen door het uitvoeren van de API-aanroep ingekapseld in retrieveEvents (). Voor elk evenement drukken we de datum af waarop het is gemaakt en vervolgens de titel.


De rest van de code is vergelijkbaar met wat we al hebben besproken en nog gemakkelijker te begrijpen. Voel je vrij om ermee te spelen voordat je doorgaat naar het volgende gedeelte.


Het organiseren van de procedurecode

Onze huidige code is in orde, maar ik denk dat we het beter kunnen doen en beter organiseren. U vindt het project met de voltooide, georganiseerde code onder de naam "GoogleCalProceduralOrganized" in de bijgevoegde broncode.

Gebruik van een globale klantvariabele

Het eerste dat me ergert aan onze ongeorganiseerde code, is dat we dit doorgeven $ client variabele in als een argument over de hele plaats, verschillende niveaus diep in geneste functies. Procedurale programmering heeft een slimme manier om dit op te lossen, een globale variabele. Sinds $ client is gedefinieerd in index.php en in de globale scope, alles wat we moeten veranderen is hoe onze functies het gebruiken. Dus in plaats van het verwachten van een $ client parameter, we kunnen gebruiken:

function printCalendars () global $ client; putTitle ('Dit zijn uw kalenders:'); foreach (getCalendarList ($ client) ['items'] als $ kalender) putLink ('? showThisCalendar ='. htmlentities ($ calendar ['id']), $ calendar ['summary']); afdrukken('
');

Vergelijk de huidige code met de nieuw georganiseerde code om het verschil te zien. In plaats van binnen te komen $ client als een parameter die we hebben gebruikt globale $ client in al onze functies en heeft deze alleen als parameter doorgegeven aan de Google API-functies. Technisch gezien zouden zelfs de Google API-functies de $ client variabele van de globale scope, maar ik denk dat het beter is om de API zo onafhankelijk mogelijk te houden.

Presentatie van logica scheiden

Sommige functies zijn duidelijk, alleen voor het afdrukken van dingen op het scherm, anderen zijn om te beslissen wat te doen, en sommige zijn een beetje van beide. Wanneer dit gebeurt, is het soms het beste om deze functies voor specifieke doeleinden naar hun eigen bestand te verplaatsen. We beginnen met de functies die puur worden gebruikt voor het afdrukken van dingen op het scherm, deze worden verplaatst naar een functions_display.php het dossier. Zie ze hieronder.

function printHome () print ('Welkom bij Google Calendar over NetTuts Example');  function printMenu () putLink ('? home', 'Home'); putLink ('? showCalendars', 'Show Calendars'); putLink ('? logout', 'Log Out'); afdrukken('

'); functie putLink ($ href, $ text) print (sprintf ('% s |', $ href, $ text)); functie putTitle ($ text) print (sprintf ('

% s

', $ tekst)); functie putBlock ($ text) print ('
'$ Tekst.'
');

De rest van dit proces van het scheiden van onze presentatie van logica vereist dat we het presentatiedeel uit onze methoden halen. Hier is hoe we het deden met een van de methoden.

functie printEventDetails () global $ client; foreach (retrievEvents ($ _ GET ['calendarId']) als $ event) if ($ event ['id'] == $ _GET ['showThisEvent']) putTitle ('Details voor event:'. $ event ['samenvatting ']); putBlock ('Dit evenement heeft status'. $ event ['status']); putBlock ('It was created at'. date ('Ymd H: m', strtotime ($ event ['created'])). 'en als laatste bijgewerkt op'. date ('Ymd H: m', strtotime ($ event ['bijgewerkt'])). '.'); putBlock ('Voor dit evenement moet dat '. $ evenement ['samenvatting']. '.'); 

Het is duidelijk dat we kunnen zien dat wat er ook in de als verklaring is gewoon presentatiecode en de rest is bedrijfslogica. In plaats van één omvangrijke functie die alles verwerkt, zullen we deze in meerdere functies opsplitsen:

functie printEventDetails () global $ client; foreach (retrievEvents ($ _ GET ['calendarId']) als $ event) if (isCurrentEvent ($ event)) putEvent ($ event);  function isCurrentEvent ($ event) return $ event ['id'] == $ _GET ['showThisEvent']; 

Na de scheiding is de bedrijfslogica nu heel eenvoudig. We hebben zelfs een kleine methode geëxtraheerd om te bepalen of de gebeurtenis de huidige is. Alle presentatiecode is nu de verantwoordelijkheid van een functie met de naam putEvent ($ event) die zich in de functions_display.php het dossier:

functie putEvent ($ event) putTitle ('Details voor event:'. $ event ['summary']); putBlock ('Dit evenement heeft status'. $ event ['status']); putBlock ('It was created at'. date ('Ymd H: m', strtotime ($ event ['created'])). 'en als laatste bijgewerkt op'. date ('Ymd H: m', strtotime ($ event ['bijgewerkt'])). '.'); putBlock ('Voor dit evenement moet dat '. $ evenement ['samenvatting']. '.'); 

Hoewel deze methode alleen informatie weergeeft, moeten we er rekening mee houden dat deze afhankelijk is van grondige kennis over de structuur van $ event. Maar dit is OK voor nu. Wat de rest van de methoden betreft, deze werden op dezelfde manier van elkaar gescheiden.

Het elimineren van lange if-else verklaringen

Het laatste dat me stoort aan onze huidige code is de lange if-else verklaring in onze doUserAction () functie, die wordt gebruikt om te beslissen wat te doen voor elke actie. Nu is PHP vrij flexibel als het gaat om metaprogrammering (functies oproepen door middel van verwijzing). Met deze truc kunnen functienamen worden gekoppeld aan de $ _GET variabele waarden. Dus we kunnen een single introduceren actie parameter in de $ _GET variabele en gebruik de waarde daarvan als functienaam.

function doUserAction () putMenu (); als (! isset ($ _ GET ['action'])) terugkeert; $ _GET [ 'action'] (); 

Op basis van deze aanpak wordt ons menu als volgt gegenereerd:

functie putMenu () putLink ('? action = putHome', 'Home'); putLink ('? action = printCalendars', 'Show Calendars'); putLink ('? logout', 'Log Out'); afdrukken('

');

Zoals je waarschijnlijk kunt zien, heeft deze reorganisatie ons al in de richting van een objectgericht ontwerp geduwd. Het is niet duidelijk wat voor soort objecten we hebben en met welk exact gedrag, maar we hebben hier en daar wat aanwijzingen.

We hebben presentaties die afhankelijk zijn van gegevenstypen uit de bedrijfslogica. Dit lijkt op de afhankelijkheidsinversie waar we het over hadden in het inleidende hoofdstuk. De stroom van de besturing is nog steeds van de bedrijfslogica naar de presentatie, maar de afhankelijkheid van de broncode begon te veranderen in een omgekeerde afhankelijkheid. Ik zou zeggen dat het op dit moment meer een bidirectionele afhankelijkheid is.

Een andere aanwijzing voor een objectgericht ontwerp is het kleine beetje metaprogrammering dat we net hebben gedaan. We noemen een methode waarvan we niets weten. Het kan van alles zijn en het is alsof we te maken hebben met een laag niveau van polymorfisme.

Afhankelijkheid analyse

Voor onze huidige code kunnen we een schema tekenen, zoals hieronder, om de eerste paar stappen in onze applicatie te illustreren. Het tekenen van alle lijnen zou te gecompliceerd zijn geweest.


We hebben blauwe lijnen gemarkeerd, de procedureaanroepen. Zoals je ziet, stromen ze in dezelfde richting als voorheen. Daarnaast hebben we de groene lijnen die indirecte oproepen aangeven. Deze gaan allemaal door doUserAction (). Deze twee soorten lijnen vertegenwoordigen de stroom van controle, en je kunt zien dat deze in principe onveranderd is.

De rode lijnen introduceren echter een ander concept. Ze vertegenwoordigen een rudimentaire broncode-afhankelijkheid. Ik bedoel rudimentair, omdat het niet zo voor de hand liggend is. De putMenu () methode bevat de namen van de functies die moeten worden aangeroepen voor die specifieke link. Dit is een afhankelijkheid en dezelfde regel is van toepassing op alle andere methoden die koppelingen maken. Ze afhangen over het gedrag van de andere functies.

Een tweede type afhankelijkheid is hier ook te zien. De afhankelijkheid van gegevens. Ik heb eerder genoemd $ kalender en $ event. De printfuncties moeten een grondige kennis hebben van de interne structuur van deze arrays om hun werk te doen.

Dus na dat alles denk ik dat we voldoende redenen hebben om verder te gaan met onze laatste stap.


Een objectgerichte oplossing

Ongeacht het gebruikte paradigma, er is geen perfecte oplossing voor een probleem. Dus hier is hoe ik voorstel om onze code op een objectgerichte manier te organiseren.

First Instinct

We zijn al begonnen met het scheiden van problemen in bedrijfslogica en presentatie. We hebben zelfs onze doUserAction () methode als een afzonderlijke entiteit. Dus mijn eerste instinct is om drie klassen te maken Presentator, Logica, en router. Deze zullen waarschijnlijk later veranderen, maar we hebben een plaats nodig om te beginnen, goed?

De router bevat slechts één methode en deze blijft redelijk vergelijkbaar met de vorige implementatie.

class Router function doUserAction () (nieuwe presentator ()) -> putMenu (); als (! isset ($ _ GET ['action'])) terugkeert; (nieuwe logica ()) -> $ _ GET ['actie'] (); 

Dus nu moeten we onze expliciet bellen putMenu () methode met behulp van een nieuw Presentator object en de rest van de acties worden aangeroepen met behulp van a Logica voorwerp. Dit veroorzaakt echter onmiddellijk een probleem. We hebben een actie die niet in de Logic-klasse valt. putHome () bevindt zich in de klasse Presenter. We moeten een actie introduceren in Logica die zal delegeren naar de presentator putHome () methode. Onthoud dat we voorlopig alleen onze bestaande code willen omzetten in de drie klassen die we hebben geïdentificeerd als mogelijke kandidaten voor een OO-ontwerp. We willen alleen doen wat absoluut noodzakelijk is om het ontwerp te laten werken. Nadat we werkcode hebben, zullen we deze verder wijzigen.

Zodra we een putHome () methode in de Logic-klasse hebben we een dilemma. Hoe methoden van Presenter aanroepen? We kunnen een Presenter-object in Logic maken en doorgeven, zodat het altijd verwijst naar de presentatie. Laten we dat doen vanaf onze router.

class Router function doUserAction () (nieuwe presentator ()) -> putMenu (); als (! isset ($ _ GET ['action'])) terugkeert; (nieuwe logica (nieuwe presentator)) -> $ _ GET ['action'] (); 

Nu kunnen we een constructor in Logic toevoegen en de delegatie toevoegen aan putHome () in Presenter.

class Logic private $ presenter; function __construct (presentator $ presentator) $ this-> presentator = $ presenter;  function putHome () $ this-> presentator-> putHome ();  [...]

Met een paar kleine aanpassingen in index.php en omdat Presenter de oude weergavemethoden omwikkelt, Logica de oude bedrijfslogica-functies verpakt en Router de oude actiekiezer omwikkelt, kunnen we onze code daadwerkelijk uitvoeren en het menu-element "Thuis" werken.

require_once './google-api-php-client/src/Google_Client.php'; require_once './google-api-php-client/src/contrib/Google_CalendarService.php'; require_once __DIR__. '/ ... /apiAccess.php'; require_once './functins_google_api.php'; require_once './Presenter.php'; require_once './Logic.php'; require_once './Router.php'; session_start (); $ client = createClient (); als (! authenticate ($ client)) terugkomt; (nieuwe router ()) -> doUserAction ();

En hier is het in actie.


Vervolgens moeten we in onze Logic-les op de juiste manier oproepen wijzigen om logica weer te geven, om mee te werken $ This-> presentator. Dan hebben we twee methoden - isCurrentEvent () en retrieveEvents () - die alleen binnen de Logic-klasse worden gebruikt. We maken ze privé en veranderen de oproepen dienovereenkomstig.

We zullen dan dezelfde aanpak volgen met de Presenter-klasse. We zullen alle oproepen wijzigen naar te wijzen methoden $ This-> iets en maak putTitle (), putLink (), en putBlock () privé, omdat ze alleen van Presenter worden gebruikt. Bekijk de code in de GoogleCalObjectOrientedInitial map in de bijgevoegde broncode als u het moeilijk vindt om al deze veranderingen zelf te doen.

Op dit moment hebben we een werkende app. Het is meestal procedurele code gewikkeld in OO-syntaxis, die nog steeds de $ client globale variabele en heeft tonnen andere anti-objectgeoriënteerde geuren, maar het werkt.

Als we het klassediagram tekenen met afhankelijkheden voor deze code, ziet het er als volgt uit:>


Zowel de stroombesturings- als broncodeafhankelijkheden gaan door de router, dan de logica en uiteindelijk door de presentatie. Met deze laatste wijziging vervaagden we eigenlijk een beetje van de afhankelijkheidsinversie die we in onze vorige stap hebben waargenomen. Maar laat je niet voor de gek houden. Het principe is er, we moeten het gewoon duidelijk maken.

De afhankelijkheid van de broncode terugzetten

Het is moeilijk om te zeggen dat het ene SOLID-principe belangrijker is dan het andere, maar ik denk dat het Principe van Inversie van Afhankelijkheid de grootste, onmiddellijke impact heeft op je ontwerp. Dit principe zegt:

EEN: Hoogwaardige modules moeten niet afhankelijk zijn van modules op een laag niveau. Beide moeten afhangen van abstracties en B: Abstracties moeten niet afhankelijk zijn van details. Details moeten afhangen van abstracties.

Simpel gezegd betekent dit dat concrete implementaties afhankelijk moeten zijn van abstracte klassen. Naarmate je lessen abstracter worden, zijn ze minder geneigd om te veranderen. Dus je kunt het probleem waarnemen: vaak veranderende klassen zouden afhankelijk moeten zijn van andere, veel stabielere klassen. Het meest volatiele deel van een toepassing is dus waarschijnlijk de gebruikersinterface, die de klasse Presenter in onze toepassing zou zijn. Laten we deze afhankelijkheidsinversie duidelijk maken.

Eerst zullen we onze router alleen de presentator laten gebruiken en de afhankelijkheid van Logic verbreken.

class Router function doUserAction () (nieuwe presentator ()) -> putMenu (); als (! isset ($ _ GET ['action'])) terugkeert; (new Presenter ()) -> $ _ GET ['action'] (); 

Vervolgens zullen we Presenter wijzigen om een ​​instantie van Logic te gebruiken en hem om de informatie vragen die hij moet presenteren. In ons geval beschouw ik het als aanvaardbaar dat Presenter het exemplaar van Logic daadwerkelijk maakt, maar in elk productiesysteem hebt u waarschijnlijk Fabrieken die bedrijfslogica-gerelateerde objecten maken en deze in de presentatielaag injecteren.

Nu, de functie putHome (), aanwezig in zowel de klassen Logica als Presenter, verdwijnen uit Logica. Dit is een goed teken, omdat we duplicatie verwijderen. De constructor en verwijzing naar Presenter verdwijnen ook uit Logica. Aan de andere kant moet een constructor die een Logic-object maakt op Presenter worden geschreven.

class Presenter private $ businessLogic; function __construct () $ this-> businessLogic = new Logic ();  function putHome () print ('Welcome to Google Calendar over NetTuts Example');  [...]

Na die wijzigingen, klikken op Toon kalenders zal echter een mooie fout veroorzaken. Omdat al onze acties vanuit de koppelingen verwijzen naar functienamen in de Logic-klasse, zullen we wat meer consistente wijzigingen moeten aanbrengen om de afhankelijkheid tussen de twee om te keren. Laten we het op één manier tegelijk doen. De eerste foutmelding zegt:

Fatale fout: aanroep op niet-gedefinieerde methode Presenter :: printCalendars () in / [...] /GoogleCalObjectOrientedFinal/Router.php op regel 9

Onze router wil dus een methode aanroepen die niet bestaat in Presenter, printCalendars (). Laten we die methode maken in Presenter en controleren wat het heeft gedaan in Logic. Er werd een titel afgedrukt en vervolgens door een aantal agenda's gefietst en gebeld putCalendar (). In Presenter de printCalendars () methode ziet er als volgt uit:

function printCalendars () $ this-> putCalendarListTitle (); foreach ($ this-> businessLogic-> getCalendars () als $ kalender) $ this-> putCalendarListElement ($ kalender); 

Aan de andere kant wordt de methode in Logic vrij anemisch. Gewoon een telefoontje naar de Google API-bibliotheek.

functie getCalendars () global $ client; return getCalendarList ($ client) ['items']; 

Dit kan betekenen dat je jezelf twee vragen stelt: "Hebben we eigenlijk een Logica-klasse nodig?" en "Heeft onze toepassing zelfs logica?". Nou, dat weten we nog niet. Voorlopig zullen we het bovenstaande proces voortzetten totdat alle code werkt en Logica niet meer afhankelijk is van Presenter.

Dus, we zullen een gebruiken printCalendarContents () methode in Presenter, zoals hieronder:

function printCalendarContents () $ this-> putCalendarTitle (); foreach ($ this-> businessLogic-> getEventsForCalendar () als $ evenement) $ this-> putEventListElement ($ event); 

Wat op zijn beurt ons in staat zal stellen het getEventsForCalendar () in Logic, in zoiets als dit.

functie getEventsForCalendar () global $ client; return getEventList ($ client, htmlspecialchars ($ _ GET ['showThisCalendar'])) ['items']; 

Nu werkt dit, maar ik maak me hier zorgen over. De $ _GET variabele wordt gebruikt in zowel de klassen Logic als Presenter. Moet niet alleen de Presenter-klasse gebruiken $ _GET? Ik bedoel, Presenter moet absoluut weten $ _GET omdat het verbindingen moet maken die dit invullen $ _GET variabel. Dus dat zou dat betekenen $ _GET is strikt HTTP-gerelateerd. Nu willen we dat onze code werkt met een CLI of grafische gebruikersinterface. Daarom willen we deze kennis alleen bij de presentator bewaren. Dit maakt de twee methoden van hierboven, transformeren in de twee hieronder.

functie getEventsForCalendar ($ calendarId) global $ client; retourneer getEventList ($ client, $ calendarId) ['items']; 
function printCalendarContents () $ this-> putCalendarTitle (); $ eventsForCalendar = $ this-> businessLogic-> getEventsForCalendar (htmlspecialchars ($ _ GET ['showThisCalendar'])); foreach ($ eventsForCalendar as $ event) $ this-> putEventListElement ($ event); 

De laatste functie waarmee we nu te maken hebben, is voor het afdrukken van een specifieke gebeurtenis. Omwille van dit voorbeeld, stel dat er geen manier is om een ​​evenement rechtstreeks op te halen en we moeten het zelf vinden. Nu komt onze Logic-klasse goed van pas. Het is een perfecte plek om lijsten van evenementen te manipuleren en te zoeken naar een specifieke ID:

functie getEventById ($ eventId, $ calendarId) foreach ($ this-> getEventsForCalendar ($ calendarId) als $ event) if ($ event ['id'] == $ eventId) return $ event; 

En dan zorgt de overeenkomstige oproep op Presenter voor het afdrukken ervan:

functie printEventDetails () $ this-> putEvent ($ this-> businessLogic-> getEventById ($ _GET ['showThisEvent'], $ _GET ['calendarId'])); 

Dat is het. Hier zijn we. Afhankelijkheid omgekeerd!


Controle loopt nog steeds van Logica naar Presenter. De gepresenteerde inhoud is volledig gedefinieerd door Logic. Als we morgen bijvoorbeeld verbinding willen maken met een andere agendaservice, kunnen we een andere logica maken, deze in Presenter injecteren en Presenter zal zelfs geen verschil merken. Ook de afhankelijkheid van de broncode is met succes omgekeerd. Presentator is de enige die creëert en direct afhankelijk is van Logica. Deze afhankelijkheid is cruciaal in het toestaan ​​dat Presenter de manier waarop gegevens worden weergegeven, wijzigt, zonder iets in Logica te bewerkstelligen. Daarnaast kunnen we onze HTML-presentator schakelen met een CLI-presentator of elke andere methode om de informatie aan de gebruiker weer te geven.

Het wegwerken van de globale variabele

Waarschijnlijk is de laatste overgebleven ernstige ontwerpfout het gebruik van een globale variabele voor $ client. Alle code in onze applicatie heeft