Polymorfisme begrijpen en toepassen in PHP

Bij objectgeoriënteerd programmeren is polymorfisme een krachtig en fundamenteel hulpmiddel. Het kan worden gebruikt om een ​​meer organische stroom in uw toepassing te creëren. Deze tutorial zal het algemene concept van polymorfisme beschrijven en hoe het gemakkelijk kan worden geïmplementeerd in PHP.


Wat is polymorfisme?

Polymorfisme is een lang woord voor een heel eenvoudig concept.

Polymorfisme beschrijft een patroon in objectgeoriënteerd programmeren waarbij klassen verschillende functionaliteit hebben terwijl ze een gemeenschappelijke interface delen.

Het mooie van polymorfisme is dat de code die met de verschillende klassen werkt, niet hoeft te weten welke klasse het gebruikt, omdat ze allemaal op dezelfde manier worden gebruikt.

Een analogie van de echte wereld voor polymorfisme is een knop. Iedereen weet hoe een knop moet worden gebruikt: je oefent er eenvoudig druk op uit. Wat een knop "doet" is echter afhankelijk van waarmee deze is verbonden en de context waarin deze wordt gebruikt, maar het resultaat heeft geen invloed op de manier waarop het wordt gebruikt. Als je baas je vertelt op een knop te drukken, heb je al alle informatie die nodig is om de taak uit te voeren.

In de programmeerwereld wordt polymorfisme gebruikt om toepassingen modulair en uitbreidbaar te maken. In plaats van rommelige voorwaardelijke beweringen die verschillende acties beschrijven, maakt u verwisselbare objecten die u selecteert op basis van uw behoeften. Dat is het basisdoel van polymorfisme.


interfaces

Een integraal onderdeel van polymorfisme is de gemeenschappelijke interface. Er zijn twee manieren om een ​​interface in PHP te definiëren: interfaces en abstracte klassen. Beide hebben hun gebruik en je kunt ze mixen en matchen zoals je wilt in je klassenhiërarchie.

Interface

Een interface is vergelijkbaar met een klasse, behalve dat deze geen code kan bevatten. Een interface kan methodenamen en argumenten definiëren, maar niet de inhoud van de methoden. Alle klassen die een interface implementeren moet implementeer alle methoden gedefinieerd door de interface. Een klasse kan meerdere interfaces implementeren.

Een interface wordt gedeclareerd met behulp van de 'interface'sleutelwoord:

interface MyInterface // methods

en is gekoppeld aan een klasse met behulp van de 'gereedschap'sleutelwoord (meerdere interfaces kunnen worden geïmplementeerd door ze gescheiden weer te geven met komma's):

class MyClass implementeert MyInterface // methods

Methoden kunnen in de interface worden gedefinieerd, net als in een klas, behalve zonder het hoofdgedeelte (het gedeelte tussen de accolades):

interface MyInterface public function doThis (); openbare functie doThat (); openbare functie setName ($ naam); 

Alle hier gedefinieerde methoden moeten worden opgenomen in elke implementatieklasse, precies zoals beschreven. (lees de opmerkingen hieronder)

// VALID class MyClass implementeert MyInterface beschermde $ naam; public function doThis () // code die dit doet public function doThat () // code die dat doet public function setName ($ name) $ this-> name = $ name;  // INVALID class MyClass implementeert MyInterface // missing doThis ()! private function doThat () // dit moet openbaar zijn!  public function setName () // mist het naamargument! 

Abstracte klasse

Een abstracte klasse is een mix tussen een interface en een klasse. Het kan zowel functionaliteit als interface definiëren (in de vorm van abstracte methoden). Lessen die een abstracte klasse uitbreiden moet implementeer alle abstracte methoden gedefinieerd in de abstracte klasse.

Een abstracte klasse wordt op dezelfde manier verklaard als klassen met de toevoeging van de 'abstract'sleutelwoord:

abstracte klasse MyAbstract // methods

en is gekoppeld aan een klasse met behulp van de 'strekt'sleutelwoord:

class MyClass breidt MyAbstract uit // class methods

Reguliere methoden kunnen worden gedefinieerd in een abstracte klasse, net als in een reguliere klas, evenals alle abstracte methoden (met behulp van de 'abstract'sleutelwoord). Abstracte methoden gedragen zich net als methoden die zijn gedefinieerd in een interface en moeten exact worden geïmplementeerd zoals gedefinieerd door het uitbreiden van klassen.

abstracte klasse MyAbstract public $ naam; public function doThis () // do this abstracte publieke functie doThat (); abstracte openbare functie setName ($ naam); 

Stap 1: Identificeer het probleem

Laten we ons voorstellen dat je een hebt Artikel klas die verantwoordelijk is voor het beheren van artikelen op uw website. Het bevat informatie over een artikel, inclusief de titel, auteur, datum en categorie. Hier is hoe het eruit ziet:

class poly_base_Article public $ title; public $ author; openbare $ datum; openbare $ categorie; openbare functie __construct ($ title, $ author, $ date, $ category = 0) $ this-> title = $ title; $ this-> author = $ author; $ this-> date = $ date; $ this-> categorie = $ categorie; 

Notitie: De voorbeeldklassen in deze zelfstudie gebruiken de naamconventie van "package_component_Class." Dit is een gebruikelijke manier om klassen in virtuele naamruimten te scheiden om botsingen met namen te voorkomen.

Nu wilt u een methode toevoegen om de informatie in verschillende indelingen uit te voeren, zoals XML en JSON. Je zou in de verleiding kunnen komen om zoiets als dit te doen:

class poly_base_Article // ... public function write ($ type) $ ret = "; switch ($ type) case 'XML': $ ret = '
'; $ ret. = ''. $ obj-> titel. ''; $ ret. = ''. $ obj-> auteur. ''; $ ret. = ''. $ obj-> datum. ''; $ ret. = ''. $ obj-> categorie. ''; $ ret. = '
'; breken; case 'JSON': $ array = array ('article' => $ obj); $ ret = json_encode ($ array); breken; retourneer $ ret;

Dit is een soort van lelijke oplossing, maar het werkt - voor nu. Vraag jezelf af wat er in de toekomst gebeurt als we meer formaten willen toevoegen? Je kunt de les blijven bewerken, meer en meer cases toevoegen, maar nu verdun je alleen je klas.

Een belangrijk principe van OOP is dat een klasse één ding zou moeten doen, en het zou het goed moeten doen.

Met dit in gedachten moeten conditionele statements een rode vlag zijn die aangeeft dat je klas te veel verschillende dingen probeert te doen. Dit is waar polymorfisme binnenkomt.

In ons voorbeeld is het duidelijk dat er twee taken worden gepresenteerd: het beheren van artikelen en het formatteren van hun gegevens. In deze zelfstudie zullen we onze opmaakcode herformuleren in een nieuwe reeks klassen en ontdekken hoe eenvoudig het is om polymorfisme te gebruiken.


Stap 2: definieer uw interface

Het eerste wat we moeten doen is de interface definiëren. Het is belangrijk om goed na te denken over uw interface, omdat voor eventuele wijzigingen in het wijzigen van de oproepcode. In ons voorbeeld gebruiken we een eenvoudige interface om onze enige methode te definiëren:

interface poly_writer_Writer public function write (poly_base_Article $ obj); 

Het is zo simpel; we hebben een publiek gedefinieerd schrijven() methode die een artikelobject als argument accepteert. Alle klassen die de Writer-interface implementeren, zullen deze methode zeker hebben.

Tip: Als u het type argumenten wilt beperken dat aan uw functies en methoden kan worden doorgegeven, kunt u typehints gebruiken, zoals we in de schrijven() methode; het accepteert alleen objecten van het type poly_base_Article. Helaas wordt het hints van het retoursoort niet ondersteund in de huidige versies van PHP, dus het is aan jou om voor retourzendingen te zorgen.


Stap 3: Maak uw implementatie

Met je interface gedefinieerd, is het tijd om de klassen te maken die dingen echt doen. In ons voorbeeld hebben we twee indelingen die we willen uitvoeren. Daarom hebben we twee Writer-klassen: XMLWriter en JSONWriter. Het is aan deze om de gegevens uit het artikelartikel te halen en de informatie op te maken.

Dit is hoe XMLWriter eruit ziet:

class poly_writer_XMLWriter implementeert poly_writer_Writer public function write (poly_base_Article $ obj) $ ret = '
'; $ ret. = ''. $ obj-> titel. ''; $ ret. = ''. $ obj-> auteur. ''; $ ret. = ''. $ obj-> datum. ''; $ ret. = ''. $ obj-> categorie. ''; $ ret. = '
'; return $ ret;

Zoals je kunt zien in de klassenverklaring, gebruiken we de gereedschap sleutelwoord om onze interface te implementeren. De schrijven() methode bevat functionaliteit die specifiek is voor het formatteren van XML.

Dit is onze JSONWriter-klasse:

klasse poly_writer_JSONWriter implementeert poly_writer_Writer public function write (poly_base_Article $ obj) $ array = array ('article' => $ obj); return json_encode ($ array); 

Al onze code die specifiek is voor elk formaat, is nu opgenomen in individuele klassen. Deze klassen hebben elk de exclusieve verantwoordelijkheid om een ​​specifiek formaat te hanteren, en niets anders. Geen enkel ander deel van uw toepassing hoeft zich zorgen te maken over hoe deze werken om het te gebruiken, dankzij onze interface.


Stap 4: gebruik uw implementatie

Met onze nieuwe klassen gedefinieerd, is het tijd om onze Artikelklasse opnieuw te bezoeken. Alle code die in het origineel heeft geleefd schrijven() methode is verwerkt in onze nieuwe reeks klassen. Al onze methode moet nu doen om de nieuwe klassen te gebruiken, zoals deze:

class poly_base_Article // ... public function write (poly_writer_Writer $ writer) return $ writer-> write ($ this); 

Al deze methode accepteert nu een object van de Writer-klasse (dat wil zeggen elke klasse die de Writer-interface implementeert), noem het schrijven() methode, zichzelf passerend ($ this) als het argument verzendt u de retourwaarde rechtstreeks naar de clientcode. Het hoeft zich niet langer zorgen te maken over de details van het formatteren van gegevens en kan zich concentreren op zijn hoofdtaak.

Een schrijver verkrijgen

Je kunt je misschien afvragen waar je in eerste instantie een Writer-object voor hebt, omdat je er een moet doorgeven aan deze methode. Dat is aan jou, en er zijn veel strategieën. U kunt bijvoorbeeld een fabrieksklasse gebruiken om aanvraaggegevens te verzamelen en een object te maken:

class poly_base_Factory public static function getWriter () // verzamelaanvraag variabele $ format = $ _REQUEST ['format']; // construeer onze klassennaam en controleer het bestaan ​​ervan $ class = 'poly_writer_'. $ formaat. 'Auteur'; if (class_exists ($ class)) // retourneer een nieuw Writer-object retourneer nieuwe $ class ();  // anders falen we met het gooien van nieuwe Exception ('Unsupported format'); 

Zoals ik al zei, er zijn veel andere strategieën om te gebruiken, afhankelijk van uw vereisten. In dit voorbeeld kiest een verzoekvariabele welk formaat moet worden gebruikt. Het bouwt een klassenaam op uit de aanvraagvariabele, controleert of deze bestaat en retourneert vervolgens een nieuw Writer-object. Als er geen onder die naam bestaat, wordt een uitzondering gegenereerd om clientcode uit te laten zoeken wat te doen.


Stap 5: Alles samenvoegen

Met alles op zijn plaats, hier is hoe onze klantcode het allemaal samen zou brengen:

$ article = new poly_base_Article ('Polymorphism', 'Steve', time (), 0); probeer $ writer = poly_base_Factory :: getWriter ();  catch (Uitzondering $ e) $ writer = new poly_writer_XMLWriter ();  echo $ article-> write ($ writer);

Eerst hebben we een voorbeeld van een artikelobject gemaakt om mee te werken. Vervolgens proberen we een Writer-object van de Factory te krijgen en terug te vallen naar een standaard (XMLWriter) als er een uitzondering wordt gegenereerd. Ten slotte geven we het Writer-object door aan onze Article's schrijven() methode, het resultaat afdrukken.


Conclusie

In deze zelfstudie heb ik je een introductie gegeven van polymorfisme en een uitleg van interfaces in PHP. Ik hoop dat je je realiseert dat ik je maar één potentieel gebruiksmogelijkheid voor polymorfisme heb getoond. Er zijn veel, veel meer toepassingen. Polymorfisme is een elegante manier om te ontsnappen aan lelijke voorwaardelijke uitspraken in uw OOP-code. Het volgt het principe van het gescheiden houden van uw componenten en maakt integraal deel uit van veel ontwerppatronen. Als u vragen heeft, aarzel dan niet om te vragen in de comments!