Comprensione e applicazione del polimorfismo in PHP

Nella programmazione orientata agli oggetti, il polimorfismo è uno strumento potente e fondamentale. Può essere usato per creare un flusso più organico nella tua applicazione. Questo tutorial descriverà il concetto generale di polimorfismo e come può essere facilmente implementato in PHP.


Cos'è il polimorfismo?

Il polimorfismo è una parola lunga per un concetto molto semplice.

Il polimorfismo descrive un modello nella programmazione orientata agli oggetti in cui le classi hanno funzionalità diverse e condividono un'interfaccia comune.

La bellezza del polimorfismo è che il codice che lavora con le diverse classi non ha bisogno di sapere quale classe sta usando poiché sono tutti utilizzati allo stesso modo.

Un'analogia del mondo reale per il polimorfismo è un pulsante. Tutti sanno come usare un pulsante: si applica semplicemente la pressione ad esso. Ciò che un pulsante "fa", tuttavia, dipende da cosa è connesso e dal contesto in cui viene utilizzato, ma il risultato non influisce sul modo in cui viene utilizzato. Se il tuo capo ti dice di premere un pulsante, hai già tutte le informazioni necessarie per eseguire l'operazione.

Nel mondo della programmazione, il polimorfismo viene utilizzato per rendere le applicazioni più modulari ed estendibili. Invece di dichiarazioni condizionali disordinate che descrivono differenti corsi d'azione, crei oggetti intercambiabili che selezioni in base alle tue esigenze. Questo è l'obiettivo di base del polimorfismo.


interfacce

Una parte integrante del polimorfismo è l'interfaccia comune. Ci sono due modi per definire un'interfaccia in PHP: interfacce e classi astratte. Entrambi hanno i loro usi e puoi combinarli secondo la tua gerarchia di classi.

Interfaccia

Un'interfaccia è simile a una classe, tranne per il fatto che non può contenere codice. Un'interfaccia può definire i nomi e gli argomenti dei metodi, ma non i contenuti dei metodi. Qualsiasi classe che implementa un'interfaccia dovere implementare tutti i metodi definiti dall'interfaccia. Una classe può implementare più interfacce.

Un'interfaccia è dichiarata usando il 'interfaccia' parola chiave:

interfaccia MyInterface // metodi

ed è collegato ad una classe usando il 'attrezzi'parola chiave (è possibile implementare più interfacce elencandole separate da virgole):

classe MyClass implementa MyInterface // metodi

I metodi possono essere definiti nell'interfaccia proprio come in una classe, tranne che senza il corpo (la parte tra parentesi graffe):

interfaccia MyInterface public function doThis (); funzione pubblica doThat (); funzione pubblica setName ($ name); 

Tutti i metodi definiti qui dovranno essere inclusi in tutte le classi di implementazione esattamente come descritto. (leggi i commenti sul codice qui sotto)

// VALID classe MyClass implementa MyInterface $ protetto nome; funzione pubblica doQuesto () // codice che esegue questa funzione pubblica doThat () // codice che esegue public function setName ($ name) $ this-> nome = $ nome;  // INVALID classe MyClass implementa MyInterface // missing doThis ()! funzione privata doThat () // questo dovrebbe essere pubblico!  public function setName () // manca l'argomento name! 

Classe astratta

Una classe astratta è un mix tra un'interfaccia e una classe. Può definire la funzionalità e l'interfaccia (sotto forma di metodi astratti). Classi che estendono una classe astratta dovere implementare tutti i metodi astratti definiti nella classe astratta.

Una classe astratta è dichiarata allo stesso modo delle classi con l'aggiunta dell 'astratto' parola chiave:

classe astratta MyAbstract // metodi

ed è collegato ad una classe usando il 'si estende' parola chiave:

la classe MyClass estende MyAbstract // metodi della classe

I metodi regolari possono essere definiti in una classe astratta, proprio come in una classe regolare, così come tutti i metodi astratti (usando il 'astratto' parola chiave). I metodi astratti si comportano esattamente come i metodi definiti in un'interfaccia e devono essere implementati esattamente come definito estendendo le classi.

classe astratta MyAbstract nome pubblico $; funzione pubblica doThis () // do this abstract public function doThat (); funzione pubblica astratta setName ($ name); 

Passaggio 1: identifica il problema

Immaginiamo che tu abbia un Articolo classe che è responsabile della gestione degli articoli sul tuo sito web. Contiene informazioni su un articolo, tra cui titolo, autore, data e categoria. Ecco come appare:

class poly_base_Article public $ title; pubblico $ autore; pubblico $ data; categoria $ pubblica; funzione pubblica __construct ($ title, $ author, $ date, $ category = 0) $ this-> title = $ title; $ this-> author = $ author; $ this-> date = $ date; $ this-> category = $ category; 

Nota: Le classi di esempio in questo tutorial utilizzano la convenzione di denominazione di "package_component_Class." Questo è un modo comune per separare le classi in spazi dei nomi virtuali per evitare conflitti di nomi.

Ora si desidera aggiungere un metodo per l'output delle informazioni in diversi formati, come XML e JSON. Potresti essere tentato di fare qualcosa del genere:

class poly_base_Article // ... public function write ($ tipo) $ ret = "; switch ($ tipo) case 'XML': $ ret = '
'; $ ret. = ''. $ obj-> titolo. ''; $ ret. = ''. $ obj-> autore. ''; $ ret. = ''. $ obj-> date. ''; $ ret. = ''. $ obj-> categoria. ''; $ ret. = '
'; rompere; case 'JSON': $ array = array ('article' => $ obj); $ ret = json_encode ($ array); rompere; return $ ret;

Questa è una brutta soluzione, ma funziona - per ora. Chiediti cosa succede in futuro, però, quando vogliamo aggiungere altri formati? Puoi continuare a modificare la classe, aggiungendo sempre più casi, ma ora stai solo diluendo la tua classe.

Un importante principio di OOP è che una classe dovrebbe fare una cosa, e dovrebbe farlo bene.

Con questo in mente, le dichiarazioni condizionali dovrebbero essere una bandiera rossa che indica che la tua classe sta cercando di fare troppe cose diverse. È qui che entra in gioco il polimorfismo.

Nel nostro esempio, è chiaro che ci sono due compiti presentati: gestire gli articoli e formattare i loro dati. In questo tutorial, rifatteremo il nostro codice di formattazione in un nuovo set di classi e scopriremo quanto sia facile utilizzare il polimorfismo.


Passaggio 2: Definisci la tua interfaccia

La prima cosa che dovremmo fare è definire l'interfaccia. È importante pensare seriamente alla tua interfaccia, perché qualsiasi modifica potrebbe richiedere modifiche al codice chiamante. Nel nostro esempio, utilizzeremo un'interfaccia semplice per definire il nostro metodo:

interfaccia poly_writer_Writer public function write (poly_base_Article $ obj); 

È così semplice; abbiamo definito un pubblico Scrivi() metodo che accetta un oggetto Article come argomento. Qualsiasi classe che implementa l'interfaccia di Writer avrà sicuramente questo metodo.

Mancia: Se vuoi limitare il tipo di argomenti che possono essere passati alle tue funzioni e ai tuoi metodi, puoi usare i suggerimenti tipo, come abbiamo fatto nel Scrivi() metodo; accetta solo oggetti di tipo poly_base_Article. Sfortunatamente, il tipo di suggerimento restituito non è supportato nelle versioni correnti di PHP, quindi spetta a te prendersi cura dei valori di ritorno.


Passaggio 3: crea la tua implementazione

Con la tua interfaccia definita, è il momento di creare le classi che effettivamente fanno cose. Nel nostro esempio, abbiamo due formati che vogliamo produrre. Quindi abbiamo due classi di writer: XMLWriter e JSONWriter. Spetta a questi estrarre i dati dall'oggetto Articolo passato e formattare le informazioni.

Ecco come appare XMLWriter:

class poly_writer_XMLWriter implementa poly_writer_Writer public function write (poly_base_Article $ obj) $ ret = '
'; $ ret. = ''. $ obj-> titolo. ''; $ ret. = ''. $ obj-> autore. ''; $ ret. = ''. $ obj-> date. ''; $ ret. = ''. $ obj-> categoria. ''; $ ret. = '
'; return $ ret;

Come puoi vedere dalla dichiarazione di classe, usiamo il attrezzi parola chiave per implementare la nostra interfaccia. Il Scrivi() il metodo contiene funzionalità specifiche per la formattazione XML.

Ora ecco la nostra classe JSONWriter:

class poly_writer_JSONWriter implementa poly_writer_Writer public function write (poly_base_Article $ obj) $ array = array ('article' => $ obj); restituisce json_encode ($ array); 

Tutto il nostro codice specifico per ogni formato è ora contenuto all'interno di singole classi. Queste classi hanno ciascuna la sola responsabilità di gestire un formato specifico e nient'altro. Nessun'altra parte della tua applicazione deve preoccuparsi di come funzionano per poterlo utilizzare, grazie alla nostra interfaccia.


Passaggio 4: utilizzare l'implementazione

Con le nostre nuove classi definite, è il momento di rivisitare la nostra classe di articoli. Tutto il codice che visse nell'originale Scrivi() il metodo è stato scomposto nel nostro nuovo set di classi. Tutto ciò che il nostro metodo deve fare ora è usare le nuove classi, come questa:

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

Tutto questo metodo ora accetta un oggetto della classe Writer (ovvero qualsiasi classe che implementa l'interfaccia di Writer), chiama il suo Scrivi() metodo, passando se stesso ($ questo) come argomento, quindi inoltrare il suo valore di ritorno direttamente al codice cliente. Non deve più preoccuparsi dei dettagli della formattazione dei dati e può concentrarsi sul suo compito principale.

Ottenere uno scrittore

Forse ti starai chiedendo da dove inizi un oggetto Writer, dato che devi passare uno a questo metodo. Dipende da te, e ci sono molte strategie. Ad esempio, potresti utilizzare una classe factory per acquisire i dati della richiesta e creare un oggetto:

class poly_base_Factory public static function getWriter () // grab request variable $ format = $ _REQUEST ['format']; // costruisci il nome della tua classe e verifica la sua esistenza $ class = 'poly_writer_'. $ formato. 'Scrittore'; if (class_exists ($ class)) // restituisce un nuovo oggetto Writer restituisce new $ class ();  // altrimenti si fallisce lanciare una nuova eccezione ('Formato non supportato'); 

Come ho detto, ci sono molte altre strategie da utilizzare in base alle tue esigenze. In questo esempio, una variabile richiesta sceglie quale formato utilizzare. Costruisce un nome di classe dalla variabile request, controlla se esiste, quindi restituisce un nuovo oggetto Writer. Se non esiste nessuno con questo nome, viene generata un'eccezione per consentire al codice client di capire cosa fare.


Passaggio 5: Metti tutto insieme

Con tutto ciò che è in atto, ecco come il nostro codice cliente avrebbe messo tutto insieme:

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

Per prima cosa abbiamo creato un oggetto Articolo di esempio con cui lavorare. Quindi proviamo a ottenere un oggetto Writer dalla Factory, ritornando a un default (XMLWriter) se viene lanciata un'eccezione. Infine, passiamo l'oggetto Writer ai nostri articoli Scrivi() metodo, stampando il risultato.


Conclusione

In questo tutorial, ti ho fornito un'introduzione al polimorfismo e una spiegazione delle interfacce in PHP. Spero ti renderai conto che ti ho mostrato solo un possibile caso d'uso del polimorfismo. Ci sono molte, molte più applicazioni. Il polimorfismo è un modo elegante per sfuggire alle brutte dichiarazioni condizionali nel codice OOP. Segue il principio di tenere separati i componenti, ed è parte integrante di molti modelli di progettazione. Se avete domande, non esitate a chiedere nei commenti!