Er zijn veel artikelen die uitleggen wat ontwerppatronen zijn en hoe ze moeten worden geïmplementeerd; het web heeft nog niet zo'n artikel nodig! In plaats daarvan zullen we in dit artikel meer over de wanneer en waarom, liever dan de welke en hoe.
Ik zal verschillende situaties en use-cases presenteren voor patronen, en zal ook korte definities bieden om diegenen van jullie te helpen die niet zo bekend zijn met deze specifieke patronen. Laten we beginnen.
Opnieuw gepubliceerde zelfstudieOm de paar weken bekijken we enkele van onze favoriete lezers uit de geschiedenis van de site. Deze tutorial werd voor het eerst gepubliceerd in oktober 2012.
Dit artikel behandelt enkele van de verschillende Agile ontwerppatronen, gedocumenteerd in de boeken van Robert C. Martin. Deze patronen zijn moderne aanpassingen van de originele ontwerppatronen die zijn gedefinieerd en gedocumenteerd door The Gang of Four in 1994. Martin's patronen geven een veel recentere kijk op de patronen van de GoF en ze werken beter met moderne programmeertechnieken en problemen. In feite werden ongeveer 15% van de originele patronen vervangen door nieuwere patronen en de resterende patronen werden enigszins gemoderniseerd.
Het fabriekspatroon is uitgevonden om programmeurs te helpen bij het organiseren van de informatie met betrekking tot het maken van objecten. Objecten hebben soms veel constructorparameters; soms moeten ze onmiddellijk na hun creatie worden gevuld met standaardinformatie. Deze objecten moeten in fabrieken worden gemaakt, waarbij alle informatie over hun creatie en initialisatie op één plaats wordt bewaard.
Wanneer Gebruik een fabriekspatroon wanneer u merkt dat u code schrijft om informatie te verzamelen die nodig is om objecten te maken.
Waarom: Fabrieken helpen de logica van het creëren van objecten op één plek te beheersen. Ze kunnen ook afhankelijkheden doorbreken om losse koppeling en injectie van afhankelijkheid mogelijk te maken, zodat ze beter kunnen worden getest.
Er zijn twee veelgebruikte patronen om informatie op te halen uit een persistentielaag of externe gegevensbron.
Dit patroon definieert een communicatiekanaal tussen een persistentie-oplossing en de bedrijfslogica. Voor eenvoudigere toepassingen kan het hele objecten zelf ophalen of recreëren, maar het maken van objecten is de verantwoordelijkheid van fabrieken in de meest complexe toepassingen. Gateways halen eenvoudig ruwe gegevens op en houden deze bij.
Wanneer: Wanneer u informatie moet ophalen of bewaren.
Waarom: Het biedt een eenvoudige openbare interface voor gecompliceerde persistentieactiviteiten. Het kapselt ook persistentiekennis in en ontkoppelt bedrijfslogica van persistentielogica.
In feite is het gatewaypatroon slechts een specifieke implementatie van een ander ontwerppatroon dat we binnenkort zullen bespreken: het adapterpatroon.
Er zijn momenten waarop u de kennis van de persistentielaag niet aan uw bedrijfsklassen kunt (of niet wilt) geven. Het proxypatroon is een goede manier om uw bedrijfsklassen voor de gek te houden door te denken dat ze reeds bestaande objecten gebruiken.
Wanneer: U moet informatie ophalen van een persistentielaag of externe bron, maar wilt niet dat uw bedrijfslogica dit weet.
Waarom: Een niet-indringende benadering bieden voor het maken van objecten achter de schermen. Het opent ook de mogelijkheid om dit voorwerp vluchtig op te halen, lui en uit verschillende bronnen.
Een proxy implementeert effectief dezelfde interface als een echt object en bootst de functionaliteit ervan na. De bedrijfslogica gebruikt het eenvoudig alsof het een echt object is, maar in feite maakt de proxy het object als er geen bestaat.
Het actieve objectpatroon speelde ook een rol in vroege multitasking-systemen.
Oke oke. Zo goed en zo, maar hoe kunnen we de objecten vinden die we moeten maken?
Het repository-patroon is erg handig voor het implementeren van zoekmethoden en mini-query-talen. Het neemt deze query's en maakt gebruik van een gateway om de gegevens voor een fabriek te verkrijgen om de objecten te produceren die u nodig hebt.
Het repository-patroon is anders dan de andere patronen; het bestaat als onderdeel van Domain Driven Design (DDD) en is niet opgenomen als onderdeel van het boek van Robert C. Martin.
Wanneer: U moet meerdere objecten maken op basis van zoekcriteria of wanneer u meerdere objecten in de persistentielaag wilt opslaan.
Waarom: Om clients die specifieke objecten nodig hebben te laten werken met een gemeenschappelijke en goed geïsoleerde query- en persistentietaal. Het verwijdert nog meer creatiegerelateerde code uit de bedrijfslogica.
Maar wat als de repository de objecten niet kan vinden? Een optie zou zijn om een NUL
waarde, maar dit heeft twee neveneffecten:
als (is_null ($ param)) terugkomt;
) in uw code.Een betere aanpak is om een nul
voorwerp.
Een null-object implementeert dezelfde interface van uw andere objecten, maar de leden van het object keren een neutrale waarde terug. Een methode die bijvoorbeeld een tekenreeks retourneert, retourneert een lege reeks; een ander lid dat een numerieke waarde retourneert, geeft nul terug. Dit dwingt u om methoden te implementeren die geen betekenisvolle gegevens retourneren, maar u kunt deze objecten gebruiken zonder u zorgen te hoeven maken over geweigerde legaten of uw code te vervuilen met lege controles.
Wanneer: U controleert regelmatig op nul
of je hebt legaten geweigerd.
Waarom: Het kan duidelijkheid toevoegen aan uw code en dwingt u om meer na te denken over het gedrag van uw objecten.
Het is niet ongebruikelijk om veel methoden op een object te callen voordat het zijn werk kan doen. Er zijn situaties waarin u een object moet voorbereiden nadat het is gemaakt voordat u het echt kunt gebruiken. Dit leidt tot codeduplicatie bij het creëren van die objecten op verschillende plaatsen.
Wanneer: Wanneer u veel bewerkingen moet uitvoeren om objecten gereed te maken voor gebruik.
Waarom: Om de complexiteit van de consumerende code naar de creatiecode te verplaatsen.
Dit klinkt goed, nietwaar? Het is zelfs heel nuttig in veel situaties. Het opdrachtpatroon wordt veel gebruikt voor het implementeren van transacties. Als je een simpele toevoegt undo ()
methode voor een opdrachtobject, kan het alle ongedaan gemaakte mutaties die het heeft uitgevoerd volgen en indien nodig ongedaan maken.
Dus nu heb je tien (of meer) opdrachtobjecten en je wilt dat ze tegelijkertijd worden uitgevoerd. Je kunt ze verzamelen tot een actief object.
Het eenvoudige en interessante actieve object heeft slechts één verantwoordelijkheid: een lijst met opdrachtobjecten bijhouden en uitvoeren.
Wanneer: Verschillende vergelijkbare objecten moeten worden uitgevoerd met een enkele opdracht.
Waarom: Het dwingt cliënten om een enkele taak uit te voeren en heeft invloed op meerdere objecten.
Een actief object verwijdert elke opdracht uit de lijst nadat de opdracht is uitgevoerd; wat betekent dat u het commando slechts eenmaal kunt uitvoeren. Enkele voorbeelden uit de echte wereld van een actief object zijn:
Ontwerppatronen zijn hier om problemen op te lossen.
kopen()
opdracht op elk product verwijdert ze uit de winkelwagen.Het actieve objectpatroon speelde ook een rol in vroege multitasking-systemen. Elk object in een actief object zou een verwijzing naar het actieve object bevatten. Ze zouden een deel van hun taken uitvoeren en zichzelf vervolgens weer in de wachtrij plaatsen. Zelfs in de systemen van vandaag kunt u een actief object gebruiken om andere objecten te laten werken terwijl u op een reactie van een andere toepassing wacht.
Ik ben er zeker van dat je de grote belofte hebt gehoord van objectgeoriënteerd programmeren: hergebruik van code. Early adopters van OOP voorzagen het gebruik van universele bibliotheken en klassen in miljoenen verschillende projecten. Nou, het is nooit gebeurd.
Dit patroon maakt gedeeltelijk hergebruik van code mogelijk. Het is praktisch met meerdere algoritmen die maar weinig van elkaar verschillen.
Wanneer: Elimineer duplicatie op een eenvoudige manier.
Waarom: Er is sprake van duplicatie en flexibiliteit is geen probleem.
Maar flexibiliteit is leuk. Wat als ik het echt nodig heb?
Wanneer: Flexibiliteit en herbruikbaarheid is belangrijker dan eenvoud.
Waarom: Gebruik het om grote, uitwisselbare brokken van gecompliceerde logica te implementeren, met behoud van een algemene algoritmesignatuur.
U kunt bijvoorbeeld een generiek aanmaken Rekenmachine
en gebruik vervolgens verschillende ComputationStrategy
objecten om de berekeningen uit te voeren. Dit is een matig gebruikt patroon en het is het meest krachtig wanneer je veel voorwaardelijk gedrag moet definiëren.
Naarmate projecten groeien, wordt het steeds moeilijker voor externe gebruikers om toegang te krijgen tot onze applicatie. Dat is een reden om een goed gedefinieerd toegangspunt aan te bieden voor de betreffende toepassing of module. Andere dergelijke redenen kunnen de wens zijn om de interne werking en structuur van de module te verbergen.
Een gevel is in wezen een API - een mooie en klantgerichte interface. Wanneer een klant een van deze leuke methoden aanroept, delegeert de façade een reeks oproepen naar de klassen die hij verbergt om de klant de vereiste informatie of het gewenste resultaat te geven.
Wanneer: Om uw API te vereenvoudigen of opzettelijk innerlijke bedrijfslogica te verbergen.
Waarom: U kunt de API en de echte implementaties en logica onafhankelijk beheren.
Controle is goed, en vaak moet je een taak uitvoeren als er iets verandert. Gebruikers moeten verwittigd worden, rode LED's moeten knipperen, een alarm moet klinken ... je snapt het.
Het populaire Laravel-raamwerk maakt uitstekend gebruik van het gevelpatroon.
Een nulobject implementeert dezelfde interface als uw andere objecten.
Het waarnemerspatroon biedt een eenvoudige manier om objecten te bewaken en acties te ondernemen wanneer de omstandigheden veranderen. Er zijn twee soorten observatorimplementaties:
Wanneer: Om een notificatiesysteem te bieden binnen uw bedrijfslogica of naar de buitenwereld.
Waarom: Het patroon biedt een manier om gebeurtenissen met een willekeurig aantal verschillende objecten te communiceren.
Use cases voor dit patroon zijn e-mailmeldingen, logboekregistratie-daemons of berichtensystemen. Natuurlijk zijn er in het echte leven talloze andere manieren om het te gebruiken.
Het waarnemerspatroon kan worden uitgebreid met een bemiddelaarspatroon. Dit patroon neemt twee objecten als parameters. De bemiddelaar schrijft zichzelf in op de eerste parameter en wanneer er een verandering gebeurt met het waargenomen object, beslist de bemiddelaar wat hij moet doen met het tweede object.
Wanneer: De getroffen objecten kunnen niet weten wat de waargenomen objecten zijn.
Waarom: Een verborgen mechanisme aanbieden om andere objecten in het systeem te beïnvloeden wanneer een object verandert.
Soms hebt u speciale objecten nodig die uniek zijn in uw toepassing en wilt u ervoor zorgen dat alle consumenten wijzigingen in deze objecten kunnen zien. U wilt ook voorkomen dat om bepaalde redenen meerdere exemplaren van dergelijke objecten worden gemaakt, zoals een lange initialisatietijd of problemen met gelijktijdige acties voor sommige externe bibliotheken.
Een singleton is een object met een particuliere constructor en een publiek getInstance ()
methode. Deze methode zorgt ervoor dat er maar één exemplaar van het object bestaat.
Wanneer: Je moet singulariteit bereiken en een cross-platform, lui geëvalueerde oplossing willen die ook de mogelijkheid biedt van creatie door afleiding.
Waarom: Om een enkel toegangspunt te bieden wanneer dat nodig is.
Een andere benadering van singulariteit is het monostaat-ontwerppatroon. Deze oplossing maakt gebruik van een truc aangeboden door objectgeoriënteerde programmeertalen. Het heeft dynamische openbare methoden die de waarden van statische privévariabelen krijgen of instellen. Dit zorgt er op zijn beurt voor dat alle instanties van dergelijke klassen dezelfde waarden delen.
Wanneer: Transparantie, derivabiliteit en polymorfisme verdienen de voorkeur samen met singulariteit.
Waarom: Verbergen van de gebruikers / clients voor het feit dat het object singulariteit biedt.
Besteed speciale aandacht aan singulariteit. Het vervuilt de algemene naamruimte en kan in de meeste gevallen worden vervangen door iets dat beter geschikt is voor die specifieke situatie.
Het repository-patroon is best handig voor het implementeren van zoekmethoden ...
Dus je hebt een schakelaar en een licht. De schakelaar kan het licht aan en uit doen, maar nu heb je een ventilator gekocht en wil je je oude schakelaar ermee gebruiken. Dat is gemakkelijk te bereiken in de fysieke wereld; neem de schakelaar, verbind de draden en altviool.
Helaas is het niet zo gemakkelijk in de programmeerwereld. Je hebt een Schakelaar
klasse en een Licht
klasse. Als jouw Schakelaar
gebruikt de Licht
, hoe kon het de Ventilator
?
Gemakkelijk! Kopieer en plak de Schakelaar
, en verander het om de. te gebruiken Ventilator
. Maar dat is codeduplicatie; het is het equivalent van het kopen van een andere schakelaar voor de ventilator. Je zou kunnen uitbreiden Schakelaar
naar FanSwitch
, en gebruik dat object in plaats daarvan. Maar wat als u een a wilt gebruiken Knop
of Afstandsbediening
, inplaats van een Schakelaar
?
Dit is het eenvoudigste patroon dat ooit is uitgevonden. Het gebruikt alleen een interface. Dat is alles, maar er zijn verschillende implementaties.
Wanneer: U moet objecten verbinden en flexibiliteit behouden.
Waarom: Omdat het de eenvoudigste manier is om flexibiliteit te bereiken, met respect voor zowel het afhankelijkheidsinversieprincipe als het open-close-principe.
PHP wordt dynamisch getypt. Dit betekent dat u interfaces kunt weglaten en verschillende objecten in dezelfde context kunt gebruiken - waarbij u een geweigerde legaat riskeert. PHP maakt echter ook de definitie van interfaces mogelijk en ik raad u aan deze geweldige functionaliteit te gebruiken om duidelijkheid te geven aan de bedoeling van uw broncode.
Maar je hebt al een stel lessen waar je mee wilt praten? Ja natuurlijk. Er zijn veel bibliotheken, API's van derden en andere modules waar je mee moet praten, maar dit betekent niet dat onze bedrijfslogica de details van dergelijke dingen moet kennen.
Het adapterpatroon creëert eenvoudig een overeenkomst tussen de bedrijfslogica en iets anders. We hebben zo'n patroon al in actie gezien: het gateway-patroon.
Wanneer: U moet een verbinding maken met een reeds bestaande en potentieel veranderende module, bibliotheek of API.
Waarom: Om ervoor te zorgen dat uw bedrijfslogica alleen kan vertrouwen op de openbare methoden die de adapter biedt, en u kunt eenvoudig de andere kant van de adapter wijzigen.
Als een van de bovenstaande patronen niet past in uw situatie, kunt u ...
Dit is een heel ingewikkeld patroon. Persoonlijk vind ik het niet leuk omdat het meestal makkelijker is om een andere benadering te kiezen. Maar voor die speciale gevallen, wanneer andere oplossingen falen, kun je het brugpatroon bekijken.
Wanneer: Het verloop van de adapter is niet genoeg en je verandert van klassen aan beide kanten van de pijp.
Waarom: Om meer flexibiliteit te bieden ten koste van een aanzienlijke complexiteit.
Bedenk dat u een script met vergelijkbare opdrachten hebt en dat u één enkele oproep wilt uitvoeren om ze uit te voeren. Wacht! Zagen we dit al niet eerder? Het actieve objectpatroon?
Ja, ja dat hebben we gedaan. Maar deze is een beetje anders. Het is het samengestelde patroon en net als het actieve objectpatroon houdt het een lijst met objecten bij. Maar als u een methode op een samengesteld object aanroept, roept u dezelfde methode op voor alle objecten zonder ze uit de lijst te verwijderen. De clients die een methode aanroepen, denken dat ze met een enkel object van dat specifieke type praten, maar in feite worden hun acties toegepast op vele, vele objecten van hetzelfde type.
Wanneer: Je moet een actie toepassen op verschillende vergelijkbare objecten.
Waarom: Om duplicatie te verminderen en te vereenvoudigen hoe vergelijkbare objecten worden aangeroepen.
Hier is een voorbeeld: je hebt een applicatie die kan creëren en plaatsen bestellingen
. Stel dat je drie bestellingen hebt: $ order1
, $ order2
en $ order3
. Je zou kunnen bellen plaats()
op elk van hen, of u kunt die orders in een $ compositeOrder
object en bel zijn plaats()
methode. Dit roept op zijn beurt de plaats()
methode op alle bevatte Bestellen
voorwerpen.
Gateways halen alleen onbewerkte gegevens op en houden deze bij.
Een Finite State-machine (FSM) is een model met een eindig aantal discrete toestanden. Het implementeren van een FSM kan moeilijk zijn, en de gemakkelijkste manier om dit te doen is betrouwbaar schakelaar
uitspraak. Elk geval
statement vertegenwoordigt een huidige status in het apparaat en weet hoe de volgende status moet worden geactiveerd.
Maar dat weten we allemaal schakelen ... case
uitspraken zijn minder wenselijk omdat ze een ongewenste hoge fan-out produceren op onze objecten. Dus vergeet het schakelen ... case
verklaring, en overweeg in plaats daarvan het staatspatroon. Het statuspatroon bestaat uit verschillende objecten: een object om dingen te coördineren, een interface die een abstracte staat vertegenwoordigt en vervolgens verschillende implementaties - één voor elke staat. Elke staat weet welke staat erna komt en de staat kan het coördinerende object op de hoogte stellen om zijn nieuwe status in de volgende regel in te stellen.
Wanneer: FSM-achtige logica moet worden geïmplementeerd.
Waarom: Om de problemen van een te elimineren schakelen ... case
verklaring, en om beter de betekenis van elke individuele staat in te kapselen.
Een voedselautomaat kan een hoofd
klasse met een verwijzing naar a staat
klasse. Mogelijke toestandsklassen kunnen iets zijn als: WaitingForCoin
, InsertedCoin
, SelectedProduct
, Wachten op bevestiging
, DeliveringProduct
, ReturningChange
. Elke staat voert zijn taak uit en maakt het volgende statusobject om naar de coördinatorklasse te verzenden.
Er zijn momenten waarop u klassen of modules in een toepassing implementeert, en u kunt ze niet wijzigen zonder het systeem radicaal te beïnvloeden. Maar tegelijkertijd moet u nieuwe functionaliteit toevoegen die uw gebruikers nodig hebben.
Het patroon van de binnenhuisarchitect kan in deze situaties helpen. Het is heel eenvoudig: neem bestaande functionaliteit en voeg eraan toe. Dit wordt bereikt door de oorspronkelijke klasse uit te breiden en tijdens runtime nieuwe functionaliteit te bieden. Oude clients blijven het nieuwe object gebruiken zoals het een oud object is, en nieuwe clients gebruiken zowel de oude als de nieuwe functionaliteit.
Wanneer: Je kunt oude klassen niet veranderen, maar je moet nieuw gedrag of nieuwe status implementeren.
Waarom: Het biedt een niet-opdringerige manier om nieuwe functionaliteit toe te voegen.
Een eenvoudig voorbeeld is het afdrukken van gegevens. U drukt wat informatie af naar de gebruiker als gewone tekst, maar u wilt ook de mogelijkheid bieden om in HTML af te drukken. Het patroon van de decorateur is zo'n oplossing waarmee je beide functies kunt behouden.
Als uw probleem van het uitbreiden van de functionaliteit anders is, bijvoorbeeld, hebt u een complexe boomachtige structuur van objecten en wilt u functionaliteit aan meerdere knooppunten tegelijkertijd toevoegen - een eenvoudige iteratie is niet mogelijk, maar een bezoeker kan een haalbare oplossing zijn. Het nadeel is echter dat een implementatie van een bezoekerspatroon aanpassing van de oude klasse vereist als deze niet is ontworpen om een bezoeker te accepteren.
Wanneer: Een binnenhuisarchitect is niet geschikt en wat extra complexiteit is acceptabel.
Waarom: Toegestaan en geordende benadering voor het definiëren van functionaliteit voor verschillende objecten, maar voor de prijs van hogere complexiteit.
Gebruik ontwerppatronen om uw problemen op te lossen, maar alleen als ze passen.
Ontwerppatronen helpen problemen op te lossen. Als uitvoeringsaanbeveling, noem je klassen nooit naar de patronen. Zoek in plaats daarvan de juiste namen voor de juiste abstracties. Dit helpt je om beter te onderscheiden wanneer je echt een patroon nodig hebt, in plaats van het alleen maar te implementeren omdat je het kunt.
Sommigen zeggen misschien dat als je je klas niet een naam geeft met de naam van het patroon erin, andere ontwikkelaars het moeilijk zullen hebben om je code te begrijpen. Als het moeilijk is om een patroon te herkennen, dan zit het probleem in de implementatie van het patroon.
Gebruik ontwerppatronen om uw problemen op te lossen, maar alleen als ze passen. Misbruik ze niet. U zult merken dat een meer eenvoudige oplossing past bij een klein probleem; overwegende dat je pas zult ontdekken dat je een patroon nodig hebt nadat je een paar andere oplossingen hebt geïmplementeerd.
Als je net begint met het ontwerpen van patronen, hoop ik dat dit artikel je een idee heeft gegeven over hoe patronen nuttig kunnen zijn in je toepassingen. Bedankt voor het lezen!