Whens och Whys för PHP Design Patterns

Det finns många artiklar som förklarar vilka designmönster som är och hur man implementerar dem. webben behöver inte ännu en av dessa artiklar! Istället kommer vi i denna artikel att diskutera när och Varför, snarare än som och på vilket sätt.

Jag presenterar olika situationer och användarfall för mönster, och kommer också att ge korta definitioner för att hjälpa de som inte är så bekanta med dessa specifika mönster. Låt oss börja.

Publicerad handledning

Varje par veckor besöker vi några av våra läsares favoritinlägg från hela webbplatsens historia. Denna handledning publicerades först i oktober 2012.

Denna artikel täcker några av de olika Agile Design Patterns, dokumenterad i Robert C. Martins böcker. Dessa mönster är moderna anpassningar av de ursprungliga designmönster som definieras och dokumenteras av Gang av fyra 1994. Martins mönster presenterar en mycket senare nytta av GoFs mönster, och de fungerar bättre med moderna programmeringstekniker och -problem. Faktum är att cirka 15% av de ursprungliga mönstren ersattes med nyare mönster och de återstående mönstren var lite moderniserade.


Låt oss börja genom att skapa några objekt

Använd ett fabriksmönster

Fabriksmönstret uppfanns för att hjälpa programmerare att organisera informationen relaterad till objektskapande. Objekt har ibland mycket konstruktörparametrar; andra gånger måste de fyllas med standardinformation omedelbart efter skapandet. Dessa objekt ska skapas i fabriker och hålla all information om deras skapande och initialisering på en enda plats.

När Använd ett fabriksmönster när du hittar dig själv att skriva kod för att samla in information som behövs för att skapa objekt.

Varför: Fabrikerna hjälper till att innehålla logik för att skapa objekt på en enda plats. De kan också bryta beroenden för att underlätta lös koppling och beroendeinjektion för att möjliggöra bättre testning.


Hitta de data vi behöver

Det finns två ofta använda mönster för att hämta information från ett persistenslager eller extern datakälla.

Gateway Mönstret

Detta mönster definierar en kommunikationskanal mellan en persistenslösning och affärslogiken. För enklare applikationer kan det hämta eller återskapa hela objekt i sig, men objektskapande är fabrikernas ansvar i de flesta komplexa applikationer. Gateways hämtar enkelt och fortsätter rådata.

När: När du behöver hämta eller fortsätta information.

Varför: Det erbjuder ett enkelt offentligt gränssnitt för komplicerade persistensoperationer. Det inkapslar också uthållighetskunskap och avvecklar affärslogik från persistenslogik.

Faktum är att gatewaymönstret bara är ett särskilt genomförande av ett annat designmönster som vi diskuterar inom kort: adaptermönstret.

Gå med proxyen

Det finns tillfällen då du inte kan (eller inte vill) exponera kunskapen om persistensskiktet i dina företagsklasser. Proxy-mönstret är ett bra sätt att lura dina företagsklasser för att tro att de redan använder befintliga objekt.

När: Du måste hämta information från ett persistenslager eller en extern källa, men vill inte att din affärslogik ska veta detta.

Varför: Att erbjuda ett icke-påträngande sätt att skapa objekt bakom kulisserna. Det öppnar också möjligheten att hämta objektet på flugan, löjligt och från olika källor.

En proxy implementerar effektivt samma gränssnitt som ett verkligt objekt och efterliknar dess funktionalitet. Affärslogiken använder det helt enkelt som om det var ett verkligt objekt, men i själva verket skapar proxy objektet om man inte existerar.

Det aktiva objektmönstret spelade också en roll i tidiga multi-tasking-system.

Okej okej. Det stora och alla, men hur kan vi hitta de objekt som vi behöver skapa?

Fråga ett arkiv

Lagringsmönstret är mycket användbart för att implementera sökmetoder och minisökningsspråk. Det tar dessa frågor och använder en gateway för att få data för en fabrik att producera objekten du behöver.

Förvarsmönstret skiljer sig från de andra mönstren; Den existerar som en del av Domain Driven Design (DDD), och ingår inte som en del av Robert C. Martins bok.

När: Du måste skapa flera objekt baserat på sökkriterier, eller när du behöver spara flera objekt till persistensskiktet.

Varför: Att låta kunder som behöver specifika objekt att arbeta med ett gemensamt och väl isolerat sök- och persistensspråk. Det tar bort ännu mer skapningsrelaterad kod från affärslogiken.

Men vad händer om förvaret inte kan hitta föremålen? Ett alternativ skulle vara att returnera a NULL värde, men det har två biverkningar:

  • Det kastar en vägrade erövring om du försöker ringa en metod på ett sådant objekt.
  • Det tvingar dig att inkludera många nollkontroller (om (is_null ($ param)) returnerar;) i din kod.

Ett bättre tillvägagångssätt är att återvända a null objekt.

Null Object Pattern

Ett null objekt implementerar samma gränssnitt för dina andra objekt, men objektets medlemmar returnerar ett neutralt värde. Till exempel kommer en metod som returnerar en sträng att returnera en tom sträng; En annan medlem som returnerar ett numeriskt värde skulle returnera noll. Detta tvingar dig att implementera metoder som inte returnerar meningsfulla data, men du kan använda dessa objekt utan att oroa dig för vägran att erövra eller kasta din kod med nollkontroller.

När: Du kontrollerar ofta null eller du har vägrade erövringar.

Varför: Det kan lägga klarhet i din kod och tvingar dig att tänka mer om beteendet hos dina föremål.

Det är inte ovanligt att ringa många metoder på ett objekt innan det kan göra sitt jobb. Det finns situationer när du måste förbereda ett objekt efter skapandet innan du verkligen kan använda det. Detta leder till koddubbling när du skapar dessa objekt på olika ställen.

Du behöver kommandotypen

När: När du måste utföra många operationer för att förbereda objekt för användning.

Varför: För att flytta komplexitet från den konsumtande koden till skapande koden.

Det låter bra, eller hur? Det är faktiskt ganska användbart i många situationer. Kommandot mönstret används ofta för att genomföra transaktioner. Om du lägger till en enkel ångra() metod till ett kommandobjekt, kan det spåra alla de ångra transaktionerna som utförs och omvända dem om det behövs.

Så nu har du tio (eller fler) kommandobjekt, och du vill att de körs samtidigt. Du kan samla dem till ett aktivt objekt.

Det aktiva objektet

Det enkla och intressanta aktiva objektet har bara ett ansvar: Håll en lista över kommandobjekt och kör dem.

När: Flera liknande objekt måste utföras med ett enda kommando.

Varför: Det tvingar klienterna att utföra en enda uppgift och påverka flera objekt.

Ett aktivt objekt tar bort varje kommando från listan efter det att kommandot har utförts. Det betyder att du bara kan utföra kommandot en gång. Några verkliga exemplar av ett aktivt objekt är:

Designmönster är här för att lösa problem.

  • Kundvagn - Genomföra en köpa() Kommandot på varje produkt tar bort dem från vagnen.
  • Finansiella transaktioner - Gruppera transaktioner till en enda lista och genomföra dem med ett enkelt samtal till listhanterarens aktiva objekt skulle ta bort transaktionerna från köen.

Det aktiva objektmönstret spelade också en roll i tidiga multi-tasking-system. Varje objekt i ett aktivt objekt skulle hålla en referens till det aktiva objektet. De skulle genomföra en del av sina jobb och sedan lägga sig tillbaka i köen. Även i dagens system kan du använda ett aktivt objekt för att låta andra objekt fungera medan du väntar på ett svar från en annan applikation.


återanvändning

Jag är positiv att du har hört det stora löftet om objektorienterad programmering: kodåteranvändning. Tidiga adoptörer av OOP tänkte använda universella bibliotek och klasser i miljontals olika projekt. Jo det hände aldrig.

Gör några mallmetoder istället

Detta mönster möjliggör partiell återanvändning av kod. Det är praktiskt med flera algoritmer som bara skiljer sig något från varandra.

När: Eliminera dubbelarbete på ett enkelt sätt.

Varför: Det finns dubbelarbete och flexibilitet är inte ett problem.

Men flexibilitet är bra. Vad händer om jag verkligen behöver det?

Det är dags för en strategi

När: Flexibilitet och återanvändning är viktigare än enkelhet.

Varför: Använd den för att genomföra stora, utbytbara bitar av komplicerad logik, samtidigt som du behåller en gemensam algoritmsignatur.

Till exempel kan du skapa en generisk Kalkylator och använd sedan olika ComputationStrategy objekt att utföra beräkningarna. Detta är ett måttligt använt mönster, och det är mest kraftfullt när du måste definiera många villkorliga beteenden.


Upptäck-förmåga

När projekten växer blir det allt svårare för externa användare att komma åt vår ansökan. Det är en anledning att erbjuda en väldefinierad ingångspunkt till den aktuella ansökan eller modulen. Andra sådana orsaker kan innefatta en önskan att dölja modulens interna arbete och struktur.

Presentera en fasad

En fasad är i grunden ett API - ett trevligt och klientorienterat gränssnitt. När en kund ringer en av dessa fina metoder delegerar fasaden en serie samtal till de klasser som den gömmer för att ge kunden den information som krävs eller önskat resultat.

När: För att förenkla ditt API eller avsiktligt dölja inre affärslogik.

Varför: Du kan styra API: n och de verkliga implementationerna och logiken självständigt.

Kontrollen är bra, och många gånger behöver du utföra en uppgift när något ändras. Användare måste meddelas, röda lysdioder måste blinka, ett larm måste låta ... du får tanken.

Det populära Laravel-ramverket gör utmärkt användning av fasadmönstret.

Prenumerera på en Observer

Ett null objekt implementerar samma gränssnitt som dina andra objekt.

Observatormönstret erbjuder ett enkelt sätt att övervaka objekt och vidta åtgärder när förhållandena ändras. Det finns två typer av observatörsimplementeringar:

  • Polling - Objekt accepterar abonnenter. Prenumeranter observerar objektet och meddelas om specifika händelser. abonnenter fråga de observerade föremålen för mer information för att vidta åtgärder.
  • Tryck - I likhet med omröstningsmetoden accepterar objekt abonnenter, och abonnenter anmäls när en händelse inträffar. Men när en anmälan händer, Observatören får också en ledtråd som observatören kan agera på.

När: Att tillhandahålla ett anmälningssystem inom din affärslogik eller till omvärlden.

Varför: Mönstret erbjuder ett sätt att kommunicera händelser till ett antal olika objekt.

Använd fall för detta mönster är e-postmeddelanden, loggningsdemoner eller meddelandesystem. Naturligtvis finns det otaliga andra sätt att använda det i verkliga livet.

Koordinera effekterna

Observatormönstret kan utökas med ett mediatormönster. Detta mönster tar två objekt som parametrar. Mediatorn abonnerar sig på den första parametern, och när en förändring händer med det observerade objektet, bestämmer medlaren vad man ska göra på det andra objektet.

När: De drabbade objekten kan inte veta om observerade objekt.

Varför: Att erbjuda en dold mekanism att påverka andra objekt i systemet när ett objekt ändras.


Säregenhet

Ibland behöver du speciella objekt som är unika i din ansökan, och du vill se till att alla konsumenter kan se några ändringar som görs på dessa objekt. Du vill också förhindra att du skapar flera instanser av sådana objekt av vissa skäl, till exempel lång initieringstid eller problem med samtidiga åtgärder till vissa tredje partsbibliotek.

Använd en Singleton

En singleton är ett objekt som har en privat konstruktör och en allmänhet getInstance () metod. Denna metod säkerställer att endast en förekomst av objektet existerar.

När: Du behöver uppnå singularitet och vill ha en plattform, lazily utvärderad lösning som också ger möjlighet till skapande genom avledning.

Varför: Att erbjuda en enda åtkomstpunkt vid behov.

Eller skriv ett monostatobjekt

Ett annat sätt att singularitet är monostatdesignmönstret. Denna lösning använder ett trick som erbjuds av objektorienterade programmeringsspråk. Den har dynamiska offentliga metoder som får eller anger värdena för statiska privata variabler. Detta säkerställer i sin tur att alla förekomster av sådana klasser delar samma värden.

När: Transparens, derivatbarhet och polymorfism är föredragna tillsammans med singularitet.

Varför: Att dölja användarna / klienterna att objektet erbjuder singularitet.

Var särskilt uppmärksam på singularitet. Det förorenar det globala namnutrymmet och kan i de flesta fall ersättas med något som är bättre lämpat för den speciella situationen.


Styra olika objekt

Lagringsmönstret är ganska användbart för att implementera sökmetoder ...

Så du har en strömbrytare och ett ljus. Växeln kan slå på och av ljuset, men nu har du köpt en fläkt och vill använda din gamla strömbrytare med den. Det är lätt att åstadkomma i den fysiska världen; ta omkopplaren, anslut kablarna och viola.

Tyvärr är det inte så lätt i programmeringsvärlden. Du har en Växla klass och a Ljus klass. Om din Växla använder Ljus, hur kan det använda Fläkt?

Lätt! Kopiera och klistra in Växla, och ändra den för att använda Fläkt. Men det är kod duplicering; det är lika med att köpa en annan strömbrytare till fläkten. Du kan förlänga Växla till FanSwitch, och använd det objektet istället. Men vad händer om du vill använda a Knapp eller Fjärrkontroll, istället för a Växla?

Den abstrakta servermönstret

Detta är det enklaste mönstret någonsin uppfunnit. Det använder bara ett gränssnitt. Det är det, men det finns flera olika implementeringar.

När: Du måste ansluta objekt och behålla flexibilitet.

Varför: Eftersom det är det enklaste sättet att uppnå flexibilitet, samtidigt som man respekterar både beroendet om inversionsprincipen och den öppna nära principen.

PHP skrivs dynamiskt. Det innebär att du kan släppa gränssnitt och använda olika objekt i samma sammanhang - riskerar en vägran att erövra. Men även PHP möjliggör definitionen av gränssnitt, och jag rekommenderar att du använder den här stora funktionaliteten för att ge tydlighet i avsiktet på din källkod.

Men du har redan en massa klasser du vill prata med? Ja självklart. Det finns många bibliotek, tredjeparts API och andra moduler som man måste prata med, men det betyder inte att vår affärslogik måste känna till detaljerna i sådana saker.

Anslut en adapter

Adaptermönstret skapar helt enkelt en korrespondens mellan affärslogiken och något annat. Vi har redan sett ett sådant mönster i aktion: gatewaymönstret.

När: Du måste skapa en anslutning med en befintlig och potentiellt ändringsmodul, ett bibliotek eller ett API.

Varför: För att din affärslogik ska kunna förlita sig endast på de offentliga metoderna, erbjuder adaptern och tillåter enkelt att byta ut den andra sidan av adaptern.

Om någon av ovanstående mönster inte passar din situation kan du använda ...

Bridge Pattern

Detta är ett mycket komplicerat mönster. Jag tycker inte om det personligen eftersom det oftast är lättare att ta en annan inställning. Men för de speciella fallen, när andra lösningar misslyckas, kan du överväga bromönstret.

När: Adaptermönstret räcker inte, och du byter klasser på båda sidor av röret.

Varför: Att erbjuda ökad flexibilitet till kostnaden för väsentlig komplexitet.


Kompositmönstret

Tänk på att du har ett manus med liknande kommandon, och du vill göra ett samtal för att köra dem. Vänta! Såg vi inte redan så här tidigare? Det aktiva objektmönstret?

Ja, det gjorde vi. Men den här är lite annorlunda. Det är kompositmönstret, och som det aktiva objektmönstret håller det en lista över objekt. Men att anropa en metod på ett sammansatt objekt kallar samma metod på alla dess objekt utan att ta bort dem från listan. Klienterna som kallar en metod tror att de pratar med ett enda objekt av den specifika typen, men i själva verket tillämpas deras handlingar på många, många föremål av samma typ.

När: Du måste ange en åtgärd för flera liknande föremål.

Varför: För att minska dubbelarbete och förenkla hur liknande objekt heter.

Här är ett exempel: du har en applikation som kan skapa och placera Order. Antag att du har tre order: $ order1, $ order2 och $ order3. Du kan ringa plats() på var och en av dem, eller du kan innehålla dessa order i en $ compositeOrder objekt och ring det plats() metod. Detta kallar i sin tur plats() metod på alla de innehöll Ordning objekt.


Statsmönstret

Gateways hämtar bara och fortsätter rådata.

En ändlig statlig maskin (FSM) är en modell som har ett begränsat antal diskreta tillstånd. Genomförandet av en FSM kan vara svårt, och det enklaste sättet att göra det innebär att de är pålitliga växla påstående. Varje fall uttalandet representerar ett aktuellt tillstånd i maskinen, och det vet hur man aktiverar nästa tillstånd.

Men det vet vi alla switch fallet uttalanden är mindre önskvärda eftersom de ger en oönskad hög fan-out på våra föremål. Så glöm det switch fallet uttalande och istället överväga statsmönstret. Statmönstret består av flera objekt: ett objekt att samordna saker, ett gränssnitt som representerar ett abstrakt tillstånd och sedan flera implementeringar - en för varje stat. Varje stat vet vilken stat som kommer efter det, och staten kan meddela det samordnande objektet att ställa sitt nya tillstånd till nästa i rad.

När: FSM-liknande logik krävs för att implementeras.

Varför: För att eliminera problemen med a switch fallet uttalande, och för att bättre inkapsla betydelsen av varje enskilt tillstånd.

En matdistributör kunde ha en huvud klass som hänvisar till a stat klass. Möjliga statsklasser kan vara något som: WaitingForCoin, InsertedCoin, SelectedProduct, Väntar på bekräftelse, DeliveringProduct, ReturningChange. Varje stat utför sitt jobb och skapar nästa statliga objekt för att skicka till koordinatorklassen.


Dekorera med dekoratorns mönster

Det finns tillfällen då du distribuerar klasser eller moduler genom en applikation, och du kan inte ändra dem utan att radikalt påverka systemet. Men samtidigt måste du lägga till ny funktionalitet som dina användare behöver.

Dekoratorns mönster kan hjälpa till i dessa situationer. Det är väldigt enkelt: ta befintlig funktionalitet och lägg till den. Detta uppnås genom att utöka originalklassen och ge ny funktionalitet vid körtid. Gamla kunder fortsätter att använda det nya objektet som de skulle ha en gammal, och nya kunder kommer att använda både den gamla och den nya funktionaliteten.

När: Du kan inte ändra gamla klasser, men du måste genomföra nytt beteende eller tillstånd.

Varför: Det erbjuder ett ointrusivt sätt att lägga till ny funktionalitet.

Ett enkelt exempel är utskrift av data. Du skriver ut någon information till användaren som vanlig text, men du vill också ge möjlighet att skriva ut i HTML. Dekoratormönstret är en sådan lösning som låter dig behålla båda funktionerna.

Eller acceptera en besökare

Om ditt problem med att utvidga funktionaliteten är annorlunda, säger du att du har en komplex trädliknande struktur av objekt, och du vill lägga till funktionalitet för många noder samtidigt. En enkel iteration är inte möjlig, men en besökare kan vara en hållbar lösning. Nackdelen är emellertid att en besökarmönsterimplementering kräver modifikation till den gamla klassen om den inte är avsedd för att acceptera en besökare.

När: En dekoratör är inte lämplig och en viss extra komplexitet är acceptabelt.

Varför: Att tillåta och organiserade tillvägagångssätt för att definiera funktionalitet för flera objekt men till priset av högre komplexitet.


Slutsats

Använd designmönster för att lösa dina problem, men bara om de passar.

Designmönster hjälper till att lösa problem. Som en implementeringsrekommendation, namnge aldrig dina klasser efter mönstren. Istället hitta rätt namn för rätt abstraktioner. Detta hjälper dig att bättre skilja när du verkligen behöver ett mönster i motsats till att bara implementera en eftersom du kan.

Vissa kan säga att om du inte heter din klass med mönstrets namn i det, kommer andra utvecklare att ha svårt att förstå din kod. Om det är svårt att känna igen ett mönster, är problemet i mönstrets genomförande.

Använd designmönster för att lösa dina problem, men bara om de passar. Missa inte dem. Du kommer att upptäcka att en enklare lösning är ett litet problem; medan du kommer upptäcka att du bara behöver ett mönster efter att du har genomfört några andra lösningar.

Om du är ny för att designa mönster, hoppas jag att den här artikeln har gett dig en uppfattning om hur mönster kan vara till hjälp i dina applikationer. Tack för att du läser!