Denne opplæringen ble inspirert av en tale gitt av Robert C. Martin som jeg så et år siden. Hovedtemaet i hans tale handler om muligheten for å plukke det siste programmeringsspråket. Han tar opp temaer som hvorfor skulle et slikt språk eksistere? Og hvordan skal det se ut? Men hvis du leser mellom linjene, var det en annen interessant ide som tok min oppmerksomhet: de begrensninger som hvert programmeringsparadigm pålegger oss på programmører. Så før vi kommer inn på hvordan vi kan gå om å konvertere en prosessbasert PHP-app til en objektorientert, vil jeg dekke litt av teori på forhånd.
Så begrenser hvert programmerings paradigme vår evne til å gjøre hva vi vil gjøre. Hver av dem tar noe bort og gir et alternativ til å oppnå det samme resultatet. Modulær programmering tar bort ubegrenset programstørrelse. Det styrker programmereren for å bruke moduler med maksimal størrelse, og hver modul avsluttes med en "go-to" -etning til en annen modul. Så den første begrensningen er på størrelse. Så tar strukturerte programmerings- og prosessorprogrammering bort "go-to" -oppstillingen og begrenser programmerer til sekvens, valg og iterasjon. Sekvenser er variable oppgaver, valg er if-else beslutninger, og iterasjoner er gjør-mens looper. Dette er byggeblokkene i de fleste programmeringsspråk og paradigmer i dag.
Objektorientert programmering tar bort pekere til funksjoner og introduserer polymorfisme. PHP bruker ikke pekere på en måte som C gjør, men en variant av disse pekene til funksjoner kan observeres i Variable Functions. Dette gjør det mulig for en programmerer å bruke verdien av en variabel som navnet på en funksjon, slik at noe slikt kan oppnås:
funksjon foo () echo "Dette er foo"; funksjonslinje ($ param) echo "Dette er linjen som sier: $ param"; $ function = 'foo'; $ Funksjon (); // Går inn i foo () $ function = 'bar'; $ Funksjon ( 'test'); // går inn i bar ()
Dette kan ikke se viktig ved første øyekast. Men tenk på hva vi kan oppnå med et så kraftig verktøy. Vi kan sende en variabel som en parameter til en funksjon, og la den funksjonen kalle den andre, referert av verdien av parameteren. Dette er utrolig. Det tillater oss å endre funksjonaliteten til en funksjon uten å vite det. Uten funksjonen selv merke noen forskjell.
Vi kan faktisk gjøre polymorfe samtaler med denne teknikken også.
Nå, i stedet for å tenke på hvilke poeng som funksjoner gir, tenk på hvordan de fungerer. Er de ikke bare skjult "go-to" uttalelser? Egentlig er de, eller i det minste er de veldig lik indirekte "go-to" s. Det er ikke veldig bra. Det vi har her er faktisk en smart måte å gjøre "go-to" uten å bruke den direkte. Jeg må innrømme at i PHP, som eksemplet ovenfor viser, er det ganske enkelt å forstå, men det kan bli forvirrende med større prosjekter, og mange forskjellige funksjoner går fra en funksjon til en annen. I C er det enda mer uklart og skjult vanskelig å forstå.
Men bare å ta bort poengene til funksjoner er ikke nok. Objektorientert programmering må gi en erstatning, og det gjør det, på en elegant måte. Det gir polymorfisme med en enkel syntaks. Og med polymorfisme kommer de største verdiobjektorienterte programmeringstilbudene: Strømmen av kontroll er i motsetning til kildekodenavhengigheten.
I bildet over illustrert vi et enkelt eksempel på hvordan polymorfe samtaler skjer i de to forskjellige paradigmene. Ved prosessorisk eller strukturell programmering er styringsflyten likt kildekodenavhengigheten. De peker begge mot den mer konkrete implementeringen av utskriftsadferdene.
I objektorientert programmering kan vi reversere kildekodenavhengigheten og gjøre det peker mot den mer abstrakte implementeringen, samtidig som styringen av kontroll peker på den mer konkrete implementeringen. Dette er viktig fordi vi vil at kontrollen skal gå og nå den mest mulig konkrete og flyktige delen av koden, slik at vi kan få resultatet nøyaktig som vi vil ha det, men i kildekoden ønsker vi det motsatte. I vår kildekode ønsker vi at de konkrete og flyktige tingene skal være ute av veien, for å være enkle å forandre og for å påvirke så lite som mulig av resten av koden. La de flyktige delene endres ofte, men hold de mer abstrakte delene uendret. Du kan lese mer om Dependency Inversion Principle i det originale forskningsskriftet skrevet av Robert C. Martin.
I dette kapitlet vil vi lage et enkelt program for å liste ut Google-kalendere og hendelsene i dem. Først vil vi ta en prosessorisk tilnærming, ved å bruke bare enkle funksjoner og unngå enhver form for klasser eller objekter. Programmet lar deg liste opp dine Google-kalendere og hendelser. Da tar vi problemet et skritt videre ved å holde vår prosesskode og begynne å organisere den ved atferd. Til slutt vil vi omdanne det til en objektorientert versjon.
Google gir en API-klient for PHP. Vi bruker den til å koble til vår Google-konto slik at vi kan manipulere kalendere der. Hvis du vil kjøre koden, må du konfigurere Google-kontoen din for å godta kalenderforespørsler.
Selv om dette er et krav til opplæringen, er det ikke hovedfaget. Så i stedet for meg å gjenta trinnene du må ta, vil jeg vise deg riktig dokumentasjon. Ikke bekymre deg, det er veldig enkelt å sette opp og det tar bare omtrent fem minutter.
PHP Google API-klientkoden er inkludert i hvert prosjekt fra eksempelkoden som er vedlagt denne opplæringen. Jeg anbefaler at du bruker den. Alternativt, hvis du er nysgjerrig på hvordan du installerer den selv, sjekk ut den offisielle dokumentasjonen.
Følg deretter instruksjonene og fyll ut informasjonen i apiAccess.php
fil. Denne filen vil bli krevd av både de prosessoriske og objektorienterte eksemplene, slik at du ikke trenger å gjenta det. Jeg dro nøklene mine der inne, slik at du lettere kan identifisere og fylle ut din.
Hvis du tilfeldigvis bruker NetBeans, forlot jeg prosjektfilene i mappene som inneholder de forskjellige eksemplene. På denne måten kan du bare åpne prosjektene og kjøre dem umiddelbart på en lokal PHP-server (PHP 5.4 er nødvendig) ved å bare velge Kjør / Kjør prosjekt.
Klientbiblioteket for å koble til Google API er objektorientert. For vårt funksjonelle eksempel skyldte jeg et lite sett med funksjoner som bryter inn i dem, funksjonalitetene vi trenger. På denne måten kan vi bruke et prosessjonslag skrevet over objektorienterte klientbiblioteket slik at vår kode ikke trenger å bruke objekter.
Hvis du vil raskt teste at koden din og tilkoblingen til Google API fungerer, kan du bare bruke koden nedenfor som din index.php
fil. Det skal liste alle kalendere du har i kontoen din. Det skal være minst en kalender med sammendrag feltet er navnet ditt. Hvis du har en kalender med kontaktens fødselsdager, kan den kanskje ikke fungere med dette Google API, men ikke panikk, bare velg en annen.
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 (); hvis (! autentiser ($ klient)) returnere; listAllCalendars ($ klient);
Dette index.php
filen blir inngangspunktet til vår søknad. Vi vil ikke bruke et webramme eller noe fancy. Vi vil bare sende ut noen HTML-kode.
Nå som vi vet hva vi bygger og hva vi skal bruke, gå videre og last ned den vedlagte kildekoden. Jeg vil gi utdrag av det, men for å se det hele, vil du ha tilgang til den opprinnelige kilden.
For denne tilnærmingen vil vi bare få ting til å fungere. Vår kode vil bli organisert på en svært rudimentær måte, med bare noen få filer, slik:
functions.php
vil huske alt som vår søknad gjør. Både rutingslogikken, presentasjonene og hva som helst verdier og oppførsel kan bli begravet der inne. Denne applikasjonen er ganske enkel, hovedlogikken er som følger.
Vi har en enkelt funksjon kalt doUserAction ()
, som bestemmer med en lang if-else
uttalelse, hvilke andre metoder å ringe basert på parametrene i FÅ
variabel. Metodene kobler seg til Google-kalenderen ved hjelp av API og skriver ut til skjermen hva vi vil be om.
funksjon printCalendarContents ($ client) putTitle ('Dette er dine hendelser for'. getCalendar ($ client, $ _GET ['showThisCalendar']) ['sammendrag']. 'calendar:'); foreach (retrieveEvents ($ client, $ _GET ['showThisCalendar']) som $ event) print (''. dato ('Y-m-d H: m', strtotime ($ event ['created'])))); putLink ('? showThisEvent ='. htmlentities ($ event ['id']). '& calendarId ='. htmlentities ($ _ GET ['showThisCalendar']), $ event ['summary']); skrive ut(''); skrive ut('
');
Dette eksemplet er trolig den mest kompliserte funksjonen i koden vår. Det kaller en hjelperfunksjon som heter putTitle ()
, som bare skriver ut noen formatert HTML for overskriften. Tittelen vil inneholde navnet på vår kalender som kan hentes ved å ringe getCalendar ()
fra functions_google_api.php
. Den returnerte kalenderen vil være en matrise som inneholder en sammendrag
felt. Det er det vi er ute etter.
De $ klient
variabel går over alt i alle våre funksjoner. Det kreves å koble til Google API. Vi vil takle dette senere.
Deretter sykler vi over alle hendelsene i den nåværende kalenderen. Denne listen over arrays er oppnådd ved å kjøre API-samtalen innkapslet i retrieveEvents ()
. For hver hendelse, skriver vi ut datoen den ble opprettet og deretter tittelen.
Resten av koden ligner på det vi allerede har diskutert og enda enklere å forstå. Vær så snill å leke med det før du går videre til neste del.
Vår nåværende kode er OK, men jeg tror vi kan gjøre det bedre og organisere det på en mer hensiktsmessig måte. Du finner prosjektet med den fullførte organiserte koden under navnet "GoogleCalProceduralOrganized" i vedlagte kildekoden.
Det første som irriterer meg om vår uorganiserte kode, er at vi passerer dette $ klient
variere som argument over hele stedet, flere nivåer dypt innenfor nestede funksjoner. Prosedyreprogrammering har en smart måte å løse dette på, en global variabel. Siden $ klient
er definert i index.php
og i det globale omfanget er alt vi trenger for å endre, hvordan våre funksjoner bruker det. Så i stedet for å forvente a $ klient
parameter, kan vi bruke:
funksjon printCalendars () global $ client; putTitle ('Dette er dine kalendere:'); foreach (getCalendarList ($ client) ['elementer'] som $ kalender) putLink ('? showThisCalendar ='. htmlentities ($ kalender ['id']), $ kalender ['sammendrag']); skrive ut('
');
Sammenlign gjeldende kode til den nylig organiserte koden for å se forskjellen. I stedet for å passere inn $ klient
Som en parameter brukte vi global $ klient
i alle våre funksjoner og bestått det som en parameter bare for Google API-funksjonene. Teknisk kunne selv Google API-funksjonene ha brukt $ klient
variabel fra det globale omfanget, men jeg synes det er bedre å holde API så selvstendig som mulig.
Noen funksjoner er tydelige, bare for å skrive ut ting på skjermen, andre er for å bestemme hva de skal gjøre, og noen er litt av begge. Når dette skjer, er det noen ganger best å flytte disse spesifikke funksjonene til egen fil. Vi starter med funksjonene som bare brukes til å skrive ut ting på skjermen, disse blir flyttet til en functions_display.php
fil. Se dem nedenfor.
funksjon printHome () print ('Velkommen til Google Kalender over NetTuts Eksempel'); funksjon printMenu () putLink ('? hjem', 'Hjem'); putLink ('? showCalendars', 'Show Calendars'); putLink ('? logout', 'Log Out'); skrive ut('
'); funksjon putLink ($ href, $ text) print (sprintf ('% s |', $ href, $ tekst)); funksjon putTitle ($ text) print (sprintf ('% s
', $ tekst)); funksjon putBlock ($ text) print (''. $ Tekst.');
Resten av denne prosessen med å skille vår presentasjon fra logikken krever at vi trekker ut presentasjonsdelen fra våre metoder. Slik har vi gjort det med en av metodene.
funksjon printEventDetails () global $ client; foreach ('ReturEvents ($ _GET [' calendarId ']) som $ event) hvis ($ event [' id '] == $ _GET [' showThisEvent ']) putTitle (' Detaljer for begivenhet: '. $ event ']); putBlock ('Denne hendelsen har status'. $ event ['status']); putBlock ('Det ble opprettet til' dato ('Ymd H: m', strtotime ($ event ['created']). 'og sist oppdatert på'. date ('Ymd H: m', strtotime ['oppdatert'])). '.'); putBlock ('For denne hendelsen må du '. $ event ['sammendrag']. '.');
Tydeligvis kan vi se at det som er inne i hvis
erklæring er bare presentasjonskode og resten er forretningslogikk. I stedet for en omfattende funksjon som håndterer alt, vil vi bryte den inn i flere funksjoner:
funksjon printEventDetails () global $ client; foreach (retrieveEvents ($ _ GET ['calendarId']) som $ event) hvis (isCurrentEvent ($ event)) putEvent ($ event); funksjonen erCurrentEvent ($ event) return $ event ['id'] == $ _GET ['showThisEvent'];
Etter separasjonen er forretningslogikken nå veldig enkel. Vi har selv hentet en liten metode for å avgjøre om hendelsen er den nåværende. Alt presentasjonskoden er nå ansvaret for en funksjon som heter putEvent ($ hendelse)
som bor i functions_display.php
fil:
funksjon putEvent ($ event) putTitle ('Detaljer for event:'. $ event ['summary']); putBlock ('Denne hendelsen har status'. $ event ['status']); putBlock ('Det ble opprettet til' dato ('Ymd H: m', strtotime ($ event ['created']). 'og sist oppdatert på'. date ('Ymd H: m', strtotime ['oppdatert'])). '.'); putBlock ('For denne hendelsen må du '. $ event ['sammendrag']. '.');
Selv om denne metoden bare viser informasjon, må vi huske på at det avhenger av intim kunnskap om strukturen til $ event
. Men dette er OK for nå. Som for resten av metodene ble de skilt på lignende måte.
Det siste som plager meg om vår nåværende kode er den lange if-else-setningen i vår doUserAction ()
funksjon, som brukes til å bestemme hva du skal gjøre for hver handling. Nå er PHP ganske fleksibel når det gjelder meta-programmering (ringerfunksjoner ved referanse). Dette trikset lar oss korrelere funksjonsnavn med $ _GET
variabelens verdier. Så vi kan introdusere en enkelt handling
parameter i $ _GET
variabel og bruk verdien fra det som et funksjonsnavn.
funksjon doUserAction () putMenu (); hvis (! isset ($ _ GET ['action'])) returnere; $ _GET [handlingen '] ();
Basert på denne tilnærmingen blir vår meny generert slik:
funksjon putMenu () putLink ('? action = putHome', 'Home'); putLink ('? action = printCalendars', 'Show Calendars'); putLink ('? logout', 'Log Out'); skrive ut('
');
Som du sikkert ser, har denne omorganiseringen allerede presset oss mot en objektorientert design. Det er ikke klart hva slags objekter vi har og med hvilken nøyaktig oppførsel, men vi har noen ledetråder her og der.
Vi har presentasjoner som avhenger av datatyper fra forretningslogikken. Dette ligner avhengighetsinversjonen vi snakket om i innledende kapittel. Strømmen av kontrollen er fortsatt fra forretningslogikken til presentasjon, men kildekodenavhengigheten begynte å formere seg til en reversert avhengighet. Jeg vil si, på dette punktet er det mer som en toveis avhengighet.
Et annet hint på en objektorientert design er den lille meta-programmeringen vi nettopp gjorde. Vi kaller en metode, som vi ikke vet noe om. Det kan være noe, og det er som om vi har å gjøre med et lavt nivå av polymorfisme.
For vår nåværende kode kan vi tegne et skjema, som det nedenfor, for å illustrere de første trinnene gjennom vår søknad. Tegning alle linjene ville ha vært for komplisert.
Vi markerer med blå linjer, prosedyren kaller. Som du ser, flyter de i samme retning som før. I tillegg har vi de grønne linjene som markerer indirekte samtaler. Disse passerer alle gjennom doUserAction ()
. Disse to typene linjer representerer styringsflyten, og du kan observere at den er i utgangspunktet uendret.
De røde linjene innfører imidlertid et annet konsept. De representerer en rudimentær kildekodeavhengighet. Jeg mener rudimentær, fordi det ikke er så åpenbart. De putMenu ()
Metoden inkluderer navnene på funksjonene som må kalles for den aktuelle lenken. Dette er en avhengighet, og samme regel gjelder alle andre metoder som lager lenker. De avhenge på oppførselen til de andre funksjonene.
En annen type avhengighet kan også ses her. Avhengigheten av data. Jeg nevnte tidligere $ kalender
og $ event
. Utskriftsfunksjonene må ha intim kunnskap om den interne strukturen til disse arrays for å gjøre jobben sin.
Så etter alt tror jeg at vi har mange grunner til å gå videre til vårt siste skritt.
Uavhengig av paradigmet i bruk, er det ingen perfekt løsning for et problem. Så her er hvordan jeg foreslår å organisere vår kode på en objektorientert måte.
Vi har allerede begynt å skille bekymringer i forretningslogikk og presentasjon. Vi presenterte selv våre doUserAction ()
metode som en egen enhet. Så mitt første instinkt er å skape tre klasser Presenter
, Logikk
, og Router
. Disse vil mest sannsynlig endres senere, men vi trenger et sted å starte, rett?
De Router
vil bare inneholde en metode og den vil forbli ganske lik den forrige implementeringen.
klasse ruter funksjon doUserAction () (new Presenter ()) -> putMenu (); hvis (! isset ($ _ GET ['action'])) returnere; (ny logikk ()) -> $ _ GET ['action'] ();
Så nå må vi eksplisitt ringe vår putMenu ()
metode ved hjelp av en ny Presenter
objekt og resten av handlingene blir kalt ved hjelp av a Logikk
gjenstand. Dette forårsaker imidlertid umiddelbart et problem. Vi har en handling som ikke er i Logic-klassen. putHome ()
er i Presenter-klassen. Vi må presentere en handling i Logikk
som vil delegere til presentatørens putHome ()
metode. Husk at vi for øyeblikket bare ønsker å pakke inn vår eksisterende kode i de tre klassene vi identifiserte som mulige kandidater til et OO-design. Vi vil bare gjøre det som er helt nødvendig for å gjøre designarbeidet. Etter at vi har arbeidskode, vil vi endre det videre.
Så snart vi legger en putHome ()
Metode i Logikk-klassen har vi et dilemma. Slik ringer du metoder fra Presenter? Vel, vi kunne lage og sende et Presenter-objekt til Logic, så det har alltid en referanse til presentasjonen. La oss gjøre det fra ruteren vår.
klasse ruter funksjon doUserAction () (new Presenter ()) -> putMenu (); hvis (! isset ($ _ GET ['action'])) returnere; (ny logikk (ny presentator)) -> $ _ GET ['action'] ();
Nå kan vi legge til en konstruktør i Logikk og legge til delegasjonen mot putHome ()
i presentatør.
klasse logikk privat $ presenterer; funksjon __construct (Presenter $ presentator) $ this-> presenter = $ presenterer; funksjon putHome () $ this-> presenter-> putHome (); [...]
Med noen få mindre justeringer i index.php
og etter å ha Presentator innpakke de gamle skjermmetodene, Logic som pakker inn de gamle forretningslogikkfunksjonene og Ruteren som pakker inn den gamle handlingsvelgeren, kan vi faktisk kjøre koden vår og ha "Hjem" menyelementet som virker.
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 (); hvis (! autentiser ($ klient)) returnere; (ny router ()) -> doUserAction ();
Og her er det i aksjon.
Deretter må vi i vår Logic-klasse skifte til samtaler for å vise logikk, for å jobbe med $ Dette-> programleder
. Da har vi to metoder - isCurrentEvent ()
og retrieveEvents ()
- som bare brukes i logikklassen. Vi vil gjøre dem private og endre samtalene tilsvarende.
Vi vil da ta samme tilnærming med Presenter-klassen. Vi vil endre alle anrop til metoder for å peke på $ Dette-> noe
og lag putTitle ()
, putLink ()
, og putBlock ()
privat, siden de bare brukes fra presentatøren. Sjekk koden i GoogleCalObjectOrientedInitial katalog i vedlagte kildekoden hvis du har det vanskelig å gjøre alle disse endringene selv.
På dette punktet har vi en fungerende app. Det er for det meste prosesskoden innpakket i OO syntaks, som fortsatt bruker $ klient
global variabel og har tonnevis med andre antiobjektorienterte lukter, men det virker.
Hvis vi tegner klassediagrammet med avhengigheter for denne koden, vil det se slik ut:>
Både flytkontroll og kildekodeavhengighet går gjennom ruteren, deretter logikken, og til slutt gjennom presentasjonen. Denne siste endringen svarte vi faktisk litt av avhengighetsinversjonen vi observerte i vårt forrige trinn. Men ikke la deg bli lurt. Prinsippet er der, vi må bare gjøre det klart.
Det er vanskelig å si at ett SOLID-prinsipp er viktigere enn et annet, men jeg tror at Dependency Inversion Principle har størst, umiddelbar innvirkning på designen din. Dette prinsippet sier:
EN: Moduler på høyt nivå bør ikke avhenge av lavt nivå moduler. Begge bør avhenge av abstraksjoner og B: Abstraksjoner bør ikke avhenge av detaljer. Detaljer bør avhenge av abstraksjoner.
For å si det enkelt betyr dette at konkrete implementeringer bør avhenge av abstrakte klasser. Etter hvert som klassene dine blir abstrakte, jo mindre har de en tendens til å forandre seg. Så du kan oppleve problemet som: ofte skiftende klasser bør avhenge av andre, mye mer stabile klasser. Så den mest flyktige delen av enhver applikasjon er sannsynligvis dens brukergrensesnitt, som vil være presentatorklassen i vår søknad. La oss gjøre denne avhengighetsinversjonen åpenbar.
Først vil vi gjøre at ruteren bruker kun presentatøren og bryter sin avhengighet av logikk.
klasse ruter funksjon doUserAction () (new Presenter ()) -> putMenu (); hvis (! isset ($ _ GET ['action'])) returnere; (ny presentator ()) -> $ _ GET ['action'] ();
Da endrer vi Presentatør for å bruke en forekomst av Logikk og spør den om den informasjonen den trenger å presentere. I vårt tilfelle anser jeg det akseptabelt for Presenter å faktisk lage forekomsten av Logic, men i et hvilket som helst produksjonssystem vil du sannsynligvis ha Fabrikker som lager forretningslogikkrelaterte objekter og injiserer dem i presentasjonslaget.
Nå, funksjonen putHome ()
, til stede i både Logic og Presenter klassene, forsvinner fra Logic. Dette er et godt tegn, da vi fjerner duplisering. Konstruktøren og referansen til Presenter forsvinner også fra Logic. På den annen side må en konstruktør som lager et logisk objekt, skrives på Presenter.
klasse Presenter private $ businessLogic; funksjon __construct () $ this-> businessLogic = new Logic (); funksjon putHome () print ('Velkommen til Google Kalender over NetTuts Eksempel'); [...]
Etter de endringene, klikker du på Vis kalendere vil imidlertid gi en fin feil. Fordi alle våre handlinger fra leddene peker på funksjonsnavn i Logic-klassen, må vi gjøre noe mer konsistente endringer for å reversere avhengigheten mellom de to. La oss ta det en metode om gangen. Den første feilmeldingen sier:
Fatal feil: Ring til udefinert metode Presenter :: printCalendars () i / [...] /GoogleCalObjectOrientedFinal/Router.php på linje 9
Så, vår Router ønsker å ringe en metode som ikke eksisterer på Presenter, printCalendars ()
. La oss lage den metoden i Presenter og se hva den gjorde i Logic. Den skrev ut en tittel og syklet deretter gjennom noen kalendere og ringte putCalendar ()
. I Presenter the printCalendars ()
Metoden vil se slik ut:
funksjon printCalendars () $ this-> putCalendarListTitle (); foreach ($ this-> businessLogic-> getCalendars () som $ kalender) $ this-> putCalendarListElement ($ kalender);
På den annen side, i Logic, blir metoden ganske anemisk. Bare en viderekobling til Google API-biblioteket.
funksjon getCalendars () global $ client; returnere getCalendarList ($ client) ['items'];
Dette kan få deg til å stille deg to spørsmål, "Har vi faktisk behov for en Logikk-klasse?" og "Har vår søknad til og med noen logikk?". Vel, det vet vi ennå ikke. For tiden vil vi fortsette prosessen over, til hele koden fungerer, og Logic er ikke avhengig av Presenter lenger.
Så, vi vil bruke en printCalendarContents ()
metode i Presenter, som den nedenfor:
funksjon printCalendarContents () $ this-> putCalendarTitle (); foreach ($ this-> businessLogic-> getEventsForCalendar () som $ event) $ this-> putEventListElement ($ event);
Som igjen vil tillate oss å forenkle getEventsForCalendar ()
i logikk, inn i noe som dette.
funksjon getEventsForCalendar () global $ client; returner getEventList ($ klient, htmlspecialchars ($ _ GET ['showThisCalendar'])) ['elementer'];
Nå fungerer dette, men jeg har en bekymring her. De $ _GET
variabel blir brukt i både logikk- og presentatorklassene. Skal ikke bare Presenter-klassen bruke $ _GET
? Jeg mener, Presenter trenger absolutt å vite om $ _GET
fordi det må skape linker som fyller dette $ _GET
variabel. Så det ville bety det $ _GET
er strengt HTTP-relatert. Nå vil vi at vår kode skal fungere med et CLI eller desktop grafisk brukergrensesnitt. Så vi vil beholde denne kunnskapen i kun presentatøren. Dette gjør de to metodene ovenfra, forvandles til de to nedenfor.
funksjon getEventsForCalendar ($ calendarId) global $ client; returnere getEventList ($ klient, $ calendarId) ['items'];
funksjon printCalendarContents () $ this-> putCalendarTitle (); $ eventsForCalendar = $ this-> businessLogic-> getEventsForCalendar (htmlspecialchars ($ _ GET ['showThisCalendar'])); foreach ($ eventsForCalendar as $ event) $ this-> putEventListElement ($ event);
Nå er den siste funksjonen vi har å håndtere, for å skrive ut en bestemt hendelse. For dette eksemplets skyld, antar at det ikke er mulig å hente en hendelse direkte, og vi må finne den av oss selv. Nå kommer vår logikkklasse til nytte. Det er et perfekt sted å manipulere lister over hendelser og søke etter en bestemt ID:
funksjon getEventById ($ eventId, $ calendarId) foreach ($ this-> getEventsForCalendar ($ calendarId) som $ event) hvis ($ event ['id'] == $ eventId) returnere $ event
Og så vil det tilsvarende anropet på Presenter sørge for å skrive ut det:
funksjon printEventDetails () $ this-> putEvent ($ this-> businessLogic-> getEventById ($ _GET ['showThisEvent'], $ _GET ['calendarId']));
Det er det. Her er vi. Avhengighet invertert!
Kontrollen strekker seg fortsatt fra Logic til Presenter. Innholdet presentert er helt definert av Logic. Hvis vi for eksempel vil koble til en annen kalendertjeneste i morgen, kan vi opprette en annen logikk, injisere den i presentatøren, og Presenter vil ikke engang merke til noen forskjell. Også kildekodeavhengigheten ble omvendt vellykket. Presenter er den eneste som oppretter og direkte avhenger av Logic. Denne avhengigheten er avgjørende for å tillate presentatøren å endre hvordan den viser data uten å gjøre noe i logikken. I tillegg tillater det oss å bytte vår HTML Presenter med en CLI Presenter eller en annen metode for å vise informasjonen til brukeren.
Sannsynligvis den siste gjenværende alvorlige designfeilen er bruken av en global variabel for $ klient
. Alle koden i vår søknad har tilgang til den. Mirakuløst er den eneste klassen faktisk trenger $ klient
er vår logikk klasse. Det åpenbare skrittet er å lage en privat klasse variabel. Men å gjøre det krever at vi propagerer $ klient
gjennom ruteren, inn i presentatøren, slik at den kan opprette et logisk objekt med $ klient
variabel. Dette løser lite av våre problemer. Vi trenger å bygge våre klasser på et isolert sted og injisere avhengighetene riktig inn i hverandre.
For en større klassestruktur ville vi bruke fabrikker, men for vårt lille eksempel, den index.php
filen vil være et flott sted å holde etableringslogikken. Og å være inngangspunktet for vår søknad, aka "main" -filen i arkitekturskjemaet på høyt nivå, er det fortsatt utenfor grensene for vår forretningslogikk.
Så vi bytter index.php
inn i koden nedenfor, holder alle innstillingene og kommandoen session_start ():
$ client = createClient (); hvis (! autentiser ($ klient)) returnere; $ logic = ny logikk ($ klient); $ presenterer = ny presentator ($ logic); (ny router ($ presenterer)) -> doUserAction ();
Og vi er ferdige. Det er sikkert noen andre ting vi kan gjøre for å gjøre vårt design enda bedre. Hvis ikke noe annet, kunne vi skrive et par tester for våre metoder på Logic-klassen. Kanskje vår logikklasse også kunne omdøpes til noe mer representativt, som GoogleCalendarGateway. Eller vi kunne lage hendelses- og kalenderklasser for å bedre kontrollere dataene og oppførselen på disse konseptene og bryte presentatørens avhengighet av en matrise for disse datatyper. En annen forbedring og utvidelse ville være å faktisk lage polymorfe handlingsklasser i stedet for bare å ringe funksjoner ved referanse fra $ _GET
. Det finnes utallige små ting vi kan gjøre for å forbedre denne enkle designen enda bedre. Jeg vil la deg få denne flotte muligheten til å eksperimentere, fra denne siste versjonen av koden du finner i vedlagte arkiv under GoogleCalObjectOrientedFinal katalog.
Hvis du er eventyrlystne, kan du utvide dette lille programmet for å koble til andre kalendertjenester og presentere informasjon på forskjellige måter og på ulike plattformer. For alle dere bruker NetBeans, inneholder hver kildekode mappe et NetBeans-prosjekt, så du bør kunne åpne dem direkte. I den endelige versjonen er PHPUnit også forberedt for deg, men i resten av prosjektene fjernet jeg det fordi vi ikke testet.
Takk for at