De 5 meest voorkomende ontwerppatronen in PHP-toepassingen

Foto door Neil Thomas op Unsplash

Als je denkt dat het nummer één patroon Singleton is, dan word je ontslagen! Het Singleton-patroon is al verouderd en niet gewenst en zelfs gehaat.

Laten we een kijkje nemen op de 5 meest gebruikte ontworpen patronen in de PHP-wereld van deze dag.

Fabriek

U moet fabrieken gebruiken wanneer u een object wilt bouwen. Dat klopt - bouwen en niet maken. U wilt geen fabriek hebben alleen om een ​​nieuw object te maken. Wanneer u het object bouwt, maakt u het eerst en initialiseert u het. Gewoonlijk vereist het om meerdere stappen uit te voeren en bepaalde logica toe te passen. Daarmee is het logisch om dat allemaal op één plek te hebben en het opnieuw te gebruiken wanneer je een nieuw object op dezelfde manier moet laten bouwen. Kortom, dat is het punt van het fabriekspatroon.

Het is een goed idee om een ​​interface voor uw fabriek te hebben en uw code ervan te laten afhangen en niet op een betonnen fabriek. Daarmee kunt u de ene fabriek gemakkelijk vervangen door een andere wanneer u het nodig hebt.

interface FriendFactoryInterface {
    public function create (): vriend
}

Vervolgens implementeren we onze fabrieksinterface met de volgende klasse:

klasse FriendFactory implementeert FriendFactoryInterface {
    public function create (): Friend {
        
        $ friend = nieuwe vriend ();
        // initialiseer je vriend
        geef $ vriend terug;
    }
}

Dat is een vrij eenvoudig en toch krachtig ontwerppatroon!

Strategie

Het wordt gebruikt om implementatiedetails van algoritmen te verbergen die nodig zijn om een ​​bewerking uit te voeren. Met strategieën kan de klant het benodigde algoritme kiezen zonder de daadwerkelijke implementatie te kennen en deze toepassen om de bewerking uit te voeren.

Stel dat we een bibliotheek moeten maken die de gegevens van de ene gegevensbron naar de andere overbrengt. We moeten bijvoorbeeld de gegevens overbrengen van de database naar het csv-bestand of van de spreadsheet naar het json-bestand. Hoe zou je dat doen?

Eerst moeten we respectieve strategieën maken om de gegevens uit de opslag te lezen. Laten we ze lezers noemen. Vervolgens moeten we respectieve strategieën maken om de gegevens naar de opslag te schrijven. Laten we ze schrijvers noemen.

Daarom hebben we 2 lezers om de gegevens uit de database of de spreadsheet te lezen. Dienovereenkomstig hebben we 2 schrijvers om de gegevens in het csv-bestand of in het json-bestand te schrijven.

Belangrijk: de klant die met onze strategieën zal werken, zou zich geen zorgen hoeven te maken over hun implementaties. Daarom moeten we ook interfaces definiëren voor onze strategieën. Op die manier zal de klant alleen de methoden kennen die door de strategie-interfaces zijn gedefinieerd en alleen ermee werken, en wat er achter de schermen gebeurt, is niet het probleem.

Ten slotte moeten we de klant maken die de benodigde strategieën selecteert op basis van waar en naar waar hij de gegevens moet overdragen.

Laten we dat allemaal in actie zien:

interface ReaderInterface {
    public function start (): nietig;
    public function read (): array;
    public function stop (): nietig;
}
interface WriterInterface {
   public function start (): nietig;
   public function write (array $ data): void;
   public function stop (): nietig;
}
class DatabaseReader implementeert ReaderInterface {
    ...
}
class SpreadsheetReader implementeert ReaderInterface {
    ...
}
class CsvWriter implementeert WriterInterface {
    ...
}
class JsonWriter implementeert WriterInterface {
    ...
}
class Transformer {
    
    ...
    public function transform (string $ from, string $ to): void {
        $ reader = $ this-> findReader ($ from);
        $ writer = $ this-> findWriter ($ to);
        
        $ Lezer worden> start ();
        $ Schrijver-> start ();
        proberen {
            foreach ($ reader-> read () als $ rij) {
                $ Schrijver-> write ($ row);
            }
         } Tenslotte {
             $ Schrijver-> stop ();
             $ Lezer worden> stop ();
         }
     }
     ...
}

Zoals u kunt zien, geeft de transformator die de klant van onze strategieën is niet echt om de implementaties waarmee hij werkt. Het enige waar het om gaat, zijn de methoden die worden gedefinieerd door onze strategie-interfaces.

Adapter

Het wordt gebruikt om van een buitenlandse interface een gemeenschappelijke interface te maken. Laten we aannemen dat u in het project de gegevens uit sommige opslag haalt met de volgende klasse.

klasse opslag {
    privé $ bron;
    
    public function __constructor (AdapterInterface $ source) {
        $ this-> source = $ source;
    }
    public function getOne (int $ id):? object {
        retourneer $ this-> source-> find ($ id);
    }
    
    public function getAll (array $ criteria = []): Collection {
        retourneer $ this-> source-> findAll ($ criteria);
    }
}

Merk op dat de opslag niet rechtstreeks met de bron werkt, maar in plaats daarvan met de adapter van de bron.

Bovendien weet de opslag niets van concrete adapters. Het verwijst alleen naar de adapterinterface. Dus de concrete implementatie van de meegeleverde adapter is er een complete black-box voor.

Hier is een voorbeeld van de adapterinterface

interface AdapterInterface {
    public function find (int $ id):? object;
    public function findAll (array $ criteria = []): Collectie;
}

Laten we aannemen dat we een bibliotheek gebruiken om toegang te krijgen tot de MySQL-database. De bibliotheek dicteert zijn eigen interface en ziet er als volgt uit:

$ row = $ mysql-> fetchRow (...);
$ data = $ mysql-> fetchAll (...);

Zoals u ziet, kunnen we deze bibliotheek niet zomaar in onze opslag integreren. We moeten er een adapter voor maken, zoals hieronder:

class MySqlAdapter implementeert AdapterInterface {
    
     ...
     public function find (int $ id):? object {
         
         $ data = $ this-> mysql-> fetchRow (['id' => $ id]);
         // enige gegevenstransformatie
     }
     public function findAll (array $ criteria = []): Collection {
              
         $ data = $ this-> mysql-> fetchAll ($ criteria);
         // enige gegevenstransformatie
     }
   
     ...
}

Daarna kunnen we het als volgt in de opslag injecteren:

$ storage = new Storage (nieuwe MySqlAdapter ($ mysql));

Als we later besluiten om een ​​andere bibliotheek te gebruiken in plaats van die, hoeven we alleen een andere adapter voor die bibliotheek te maken, net zoals we hierboven deden, en vervolgens de nieuwe adapter in de opslag te injecteren. Zoals u kunt zien, hoeven we niets aan te raken binnen de klasse Opslag om een ​​andere bibliotheek te gebruiken om de gegevens uit de database te halen. Dat is de kracht van het ontwerppatroon van de adapter!

Waarnemer

Het wordt gebruikt om de rest van het systeem te informeren over bepaalde gebeurtenissen op een bepaalde plaats. Laten we twee oplossingen voor hetzelfde probleem bekijken om een ​​beter inzicht te krijgen in de voordelen van dit patroon.

Stel dat we theater moeten maken om films aan de critici te laten zien. We definiëren de klas Theater met de aanwezige methode. Voordat we de film presenteren, willen we berichten naar de mobiele telefoons van de critici sturen. In het midden van de film willen we de film 5 minuten stoppen om de critici een pauze te gunnen. Ten slotte willen we na het einde van de film de critici vragen om hun feedback achter te laten.

Laten we eens kijken hoe dit eruit zou zien in de code:

klas theater {
   
    openbare functie aanwezig (film $ film): void {
       
        $ critics = $ movie-> getCritics ();
        $ this-> messenger-> send ($ critici, '...');

        $ Film-> play ();

        $ Film-> pauze (5);
        $ This-> voortgangs-> break ($ ​​critici)
        $ Film-> afwerking ();

        $ This-> feedback-> aanvraag ($ critici);
    }
}

Het ziet er schoon en veelbelovend uit.

Nu, na enige tijd, vertelde de baas ons dat we voordat we met de film begonnen, ook de lichten uit wilden doen. Bovendien willen we in het midden van de film de advertentie weergeven. Eindelijk, wanneer de film eindigt, willen we beginnen met het automatisch opschonen van de kamer.

Welnu, een van de problemen hier is dat we onze theaterklasse moeten aanpassen om dat te bereiken, en dat breekt de SOLID-principes. In het bijzonder breekt het het open / gesloten principe. Bovendien zal deze benadering ervoor zorgen dat de theaterklasse afhankelijk is van verschillende aanvullende diensten, wat ook niet goed is.

Wat als we de zaken op zijn kop zetten. In plaats van meer en meer complexiteit en afhankelijkheden toe te voegen aan de Theaterklasse, zullen we de complexiteit over het systeem verspreiden en daarmee de afhankelijkheden van de Theaterklasse als bonus verminderen.

Zo ziet dit er in actie uit:

klas theater {
    
    openbare functie aanwezig (film $ film): void {
        
        $ This-> getEventManager ()
            -> informeren (nieuw evenement (evenement :: START, $ film));
        $ Film-> play ();

        $ Film-> pauze (5);
        $ This-> getEventManager ()
            -> informeren (nieuw evenement (evenement :: PAUZE, $ film));
        $ Film-> afwerking ();

        $ This-> getEventManager ()
            -> informeren (nieuw evenement (evenement :: END, $ film));
    }
}
$ theater = nieuw Theater ();
$ theater
    -> getEventManager ()
    -> luisteren (Evenement :: START, nieuwe MessagesListener ())
    -> luister (evenement :: START, nieuwe LightsListener ())
    -> luister (Evenement :: PAUZE, nieuwe BreakListener ())
    -> luister (Evenement :: PAUZE, nieuwe AdvertisementListener ())
    -> luister (evenement :: END, nieuwe FeedbackListener ())
    -> luister (Event :: END, nieuwe CleaningListener ());
$ Theater-> aanwezig ($ film);

Zoals u ziet, wordt de huidige methode uiterst eenvoudig. Het maakt niet uit wat er buiten de klas gebeurt. Het doet gewoon wat het moet doen en informeert de rest van het systeem over de feiten. Wat in die feiten geïnteresseerd is, kan naar de respectieve gebeurtenissen luisteren en hiervan op de hoogte worden gebracht en doen wat het moet doen.

Met deze aanpak wordt het ook vrij eenvoudig om extra complexiteit toe te voegen. Het enige wat u hoeft te doen is een nieuwe luisteraar maken en de benodigde logica daar plaatsen.

Ik hoop dat je het waarnemerspatroon nuttig vond.

Decorateur

Het wordt gebruikt wanneer u het gedrag van een object tijdens runtime wilt aanpassen en daarmee overbodige overerving en het aantal klassen wilt verminderen. Je vraagt ​​je misschien af ​​waarom ik dat nodig heb? Welnu, het kan beter worden uitgelegd met voorbeelden.

Stel dat we klassen Window and Door hebben en beide implementeren OpenerInterface.

interface OpenerInterface {
    public function open (): nietig;
}
class Door implementeert OpenerInterface {
    public function open (): void {
        // opent de deur
    }
}
class Window implementeert OpenerInterface {
    public function open (): void {
        // opent het venster
    }
}

Zowel de ramen als de deuren hebben hetzelfde gedrag om te openen. Nu hebben we andere deuren en ramen nodig met extra functionaliteit die de gebruikers de temperatuur buiten vertelt wanneer ze de deuren of ramen openen. We kunnen dit probleem zo oplossen met overerving:

klasse SmartDoor verlengt deur {
    public function open (): void {
        ouder :: open ();
        $ This-> temperatuur ();
    }
}
klasse SmartWindow breidt venster {
    public function open (): void {
        ouder :: open ();
        $ This-> temperatuur ();
    }
}

Al met al hebben we nu in totaal 4 klassen. Met het Decorator-patroon konden we dit probleem echter alleen met 3 klassen oplossen. Hier is hoe:

klasse SmartOpener implementeert OpenerInterface {
    
    privé $ opener;
    public function __construct (OpenerInterface $ opener) {
        $ this-> opener = $ opener;
    }
    
    public function open (): void {
        $ This-> opener-> open ();
        $ This-> temperatuur ();
    }
}
$ door = new Door ();
$ window = nieuw Window ();
$ smartDoor = nieuwe SmartOpener ($ door);
$ smartWindow = new SmartOpener ($ window);

We hebben een nieuw type opener geïntroduceerd die werkt als een proxy, maar met een extra functionaliteit er bovenop. Dat is wat het lukt.

Ik hoop dat je dit artikel nuttig en interessant vond. Aarzel dan niet om te klappen en te delen op sociale netwerken.

Veel plezier met coderen! :)