Ereditarietà multipla in PHP


97

Sto cercando un modo valido e pulito per aggirare il fatto che PHP5 non supporta ancora l'ereditarietà multipla. Ecco la gerarchia delle classi:

Messaggio
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

I due tipi di classi Invitation * hanno molto in comune; Mi piacerebbe avere una classe genitore comune, Invitation, da cui erediteranno entrambi. Sfortunatamente, hanno anche molto in comune con i loro attuali antenati ... TextMessage e EmailMessage. Desiderio classico di eredità multipla qui.

Qual è l'approccio più leggero per risolvere il problema?

Grazie!


4
Non sono molti i casi in cui l'eredità (o anche l'eredità multipla) è giustificabile. Guarda i principi SOLIDI. Preferisci la composizione all'eredità.
Ondřej Mirtes

2
@ OndřejMirtes cosa intendi con "non molti casi in cui l'eredità è giustificabile"?
styler1972

12
Voglio dire, l'eredità porta più problemi che benefici (guarda il principio di sostituzione di Liskov). Puoi risolvere quasi tutto con la composizione e risparmiare un sacco di mal di testa. Anche l'ereditarietà è statica, ovvero non è possibile modificare ciò che è già scritto nel codice. Ma la composizione può essere utilizzata in fase di esecuzione e puoi scegliere le implementazioni dinamicamente, ad esempio riutilizzare la stessa classe con diversi meccanismi di memorizzazione nella cache.
Ondřej Mirtes


1
Suggerirei ai principianti di non usare mai l'ereditarietà . In generale, le uniche due situazioni in cui è consentita l'ereditarietà sono: 1) quando si
crea

Risposte:


141

Alex, la maggior parte delle volte hai bisogno di ereditarietà multipla è un segnale che la tua struttura dell'oggetto è in qualche modo errata. Nella situazione che hai delineato vedo che hai una responsabilità di classe semplicemente troppo ampia. Se Message fa parte del modello di business dell'applicazione, non dovrebbe occuparsi del rendering dell'output. Invece, potresti dividere la responsabilità e usare MessageDispatcher che invia il messaggio passato usando testo o backend html. Non conosco il tuo codice, ma fammi simulare in questo modo:

$m = new Message();
$m->type = 'text/html';
$m->from = 'John Doe <jdoe@yahoo.com>';
$m->to = 'Random Hacker <rh@gmail.com>';
$m->subject = 'Invitation email';
$m->importBody('invitation.html');

$d = new MessageDispatcher();
$d->dispatch($m);

In questo modo puoi aggiungere alcune specializzazioni alla classe Message:

$htmlIM = new InvitationHTMLMessage(); // html type, subject and body configuration in constructor
$textIM = new InvitationTextMessage(); // text type, subject and body configuration in constructor

$d = new MessageDispatcher();
$d->dispatch($htmlIM);
$d->dispatch($textIM);

Si noti che MessageDispatcher deciderà se inviare come HTML o testo normale a seconda della typeproprietà nell'oggetto Message passato.

// in MessageDispatcher class
public function dispatch(Message $m) {
    if ($m->type == 'text/plain') {
        $this->sendAsText($m);
    } elseif ($m->type == 'text/html') {
        $this->sendAsHTML($m);
    } else {
        throw new Exception("MIME type {$m->type} not supported");
    }
}

Per riassumere, la responsabilità è suddivisa tra due classi. La configurazione dei messaggi viene eseguita nella classe InvitationHTMLMessage / InvitationTextMessage e l'algoritmo di invio viene delegato al dispatcher. Questo si chiama Strategy Pattern, puoi leggere di più qui .


13
Risposta sorprendentemente ampia, grazie! Ho imparato qualcosa oggi!
Alex Weinstein

26
... So che questo è un po 'vecchio (stavo cercando per vedere se PHP avesse MI ... solo per curiosità) Non penso che questo sia un buon esempio di Strategy Pattern. Lo Strategy Pattern è progettato in modo da poter implementare una nuova "strategia" in qualsiasi momento. L'implementazione che hai fornito non presenta tale capacità. Invece, Message dovrebbe avere la funzione "send" che chiama MessageDispatcher-> dispatch () (Dispatcher sia un parametro che un membro var), e le nuove classi HTMLDispatcher e TextDispatcher implementeranno "dispatch" nei rispettivi modi (questo consente ad altri Dispatcher di fare altro lavoro)
Terence Honles

12
Sfortunatamente PHP non è eccezionale per implementare il pattern Strategy. Le lingue che supportano il sovraccarico dei metodi funzionano meglio qui - immagina di avere due metodi con lo stesso nome: invio (HTMLMessage $ m) e invio (TextMessage $) - ora in un linguaggio fortemente tipizzato il compilatore / interprete impiegherebbe automaticamente la giusta "strategia" basata su tipo di parametro. A parte questo, non credo che essere aperti all'implementazione di una nuova strategia sia l'essenza di Strategy Pattern. Certo è una cosa carina da avere, ma spesso non è un requisito.
Michał Rudnicki

2
Supponi di avere una classe Tracing(questo è solo un esempio) in cui desideri avere cose generiche come eseguire il debug in un file, inviare SMS per problemi critici e così via. Tutte le tue classi sono figli di questa classe. Supponiamo ora di voler creare una classe Exceptionche dovrebbe avere quelle funzioni (= figlio di Tracing). Questa classe deve essere figlia di Exception. Come progettate cose del genere senza eredità multipla? Sì, potresti sempre avere una soluzione, ma ti avvicinerai sempre all'hacking. E hacking = soluzione costosa a lungo termine. Fine della storia.
Olivier Pons

1
Olivier Pons, non credo che la sottoclasse Tracing sarebbe la soluzione corretta per il tuo caso d'uso. Qualcosa di semplice come avere una classe Tracing astratta con metodi statici Debug, SendSMS, ecc., Che può quindi essere chiamata dall'interno di qualsiasi altra classe con Tracing :: SendSMS () ecc. Le altre tue classi non sono 'tipi di' Tracciamento, "usano" la traccia. Nota: alcune persone potrebbero preferire un singleton rispetto ai metodi statici; Preferisco usare metodi statici rispetto ai singleton, ove possibile.

15

Forse puoi sostituire una relazione "è-un" con una relazione "ha-un"? Un invito potrebbe contenere un messaggio, ma non è necessario che sia un messaggio. Potrebbe essere confermato un messaggio di invito, che non si sposa bene con il modello Message.

Cerca "composizione vs. eredità" se hai bisogno di saperne di più.


9

Se posso citare Phil in questo thread ...

PHP, come Java, non supporta l'ereditarietà multipla.

In arrivo in PHP 5.4 ci saranno tratti che tenteranno di fornire una soluzione a questo problema.

Nel frattempo, faresti meglio a ripensare il design della tua classe. Puoi implementare più interfacce se stai cercando un'API estesa per le tue classi.

E Chris ...

PHP non supporta davvero l'ereditarietà multipla, ma ci sono alcuni modi (un po 'disordinati) per implementarla. Controlla questo URL per alcuni esempi:

http://www.jasny.net/articles/how-i-php-multiple-inheritance/

Pensavo che entrambi avessero link utili. Non vedo l'ora di provare i tratti o forse alcuni mixin ...


1
I tratti sono la strada da percorrere
Jonathan

6

Il framework Symfony ha un plugin per mixin per questo , potresti volerlo controllare - anche solo per idee, se non per usarlo.

La risposta "design pattern" è astrarre la funzionalità condivisa in un componente separato e comporre in fase di esecuzione. Pensa a un modo per astrarre la funzionalità Invitation come una classe che viene associata alle tue classi Message in un modo diverso dall'ereditarietà.


4

Sto usando i tratti in PHP 5.4 come modo per risolvere questo problema. http://php.net/manual/en/language.oop5.traits.php

Ciò consente l'ereditarietà classica con estensioni, ma offre anche la possibilità di collocare funzionalità e proprietà comuni in un "tratto". Come dice il manuale:

Traits è un meccanismo per il riutilizzo del codice in linguaggi a ereditarietà singola come PHP. Un tratto ha lo scopo di ridurre alcune limitazioni dell'ereditarietà singola consentendo a uno sviluppatore di riutilizzare set di metodi liberamente in diverse classi indipendenti che vivono in gerarchie di classi diverse.



3

Questa è sia una domanda che una soluzione ...

E la magica _ call (),metodi _get (), __set ()? Non ho ancora testato questa soluzione, ma cosa succede se crei una classe multiInherit. Una variabile protetta in una classe figlia potrebbe contenere un array di classi da ereditare. Il costruttore nella classe multi-interfaccia potrebbe creare istanze di ciascuna delle classi che vengono ereditate e collegarle a una proprietà privata, ad esempio _ext. Il metodo __call () potrebbe utilizzare la funzione method_exists () su ciascuna delle classi nell'array _ext per individuare il metodo corretto da chiamare. __get () e __set potrebbero essere usati per individuare proprietà interne, o se sei un esperto con riferimenti potresti fare in modo che le proprietà della classe figlia e le classi ereditate siano riferimenti agli stessi dati. L'ereditarietà multipla del tuo oggetto sarebbe trasparente al codice che utilizza quegli oggetti. Anche, gli oggetti interni potrebbero accedere direttamente agli oggetti ereditati, se necessario, a condizione che l'array _ext sia indicizzato in base al nome della classe. Ho immaginato di creare questa super classe e non l'ho ancora implementata perché penso che se funzionasse potrebbe portare a sviluppare alcune cattive abitudini di programmazione.


Penso che questo sia fattibile. Combinerà funzionalità di più classi, ma in realtà non le erediterà (nel senso di instanceof)
user102008

E questo sicuramente non consentirà l'override non appena nella classe interna farà una chiamata a se stesso :: <qualunque cosa>
Phil Lello

1

Ho un paio di domande da porre per chiarire cosa stai facendo:

1) Il tuo oggetto messaggio contiene solo un messaggio, ad es. Corpo, destinatario, orario di pianificazione? 2) Cosa intendi fare con il tuo oggetto Invito? Deve essere trattato in modo speciale rispetto a un messaggio di posta elettronica? 3) In caso affermativo COSA ha di così speciale? 4) In tal caso, perché i tipi di messaggio devono essere gestiti in modo diverso per un invito? 5) Cosa succede se si desidera inviare un messaggio di benvenuto o un messaggio OK? Sono anche oggetti nuovi?

Sembra che tu stia cercando di combinare troppe funzionalità in un insieme di oggetti che dovrebbero riguardare solo il contenuto di un messaggio e non come dovrebbe essere gestito. Per me, vedi, non c'è differenza tra un invito o un messaggio standard. Se l'invito richiede una gestione speciale, significa logica dell'applicazione e non un tipo di messaggio.

Ad esempio: un sistema che ho creato aveva un oggetto messaggio di base condiviso esteso a SMS, e-mail e altri tipi di messaggi. Tuttavia: questi non sono stati estesi ulteriormente: un messaggio di invito era semplicemente un testo predefinito da inviare tramite un messaggio di tipo E-mail. Una specifica domanda di invito riguarderebbe la convalida e altri requisiti per un invito. Dopotutto, tutto ciò che vuoi fare è inviare il messaggio X al destinatario Y che dovrebbe essere un sistema discreto a sé stante.


0

Stesso problema come Java. Prova a utilizzare interfacce con funzioni astratte per risolvere il problema


0

PHP supporta le interfacce. Questa potrebbe essere una buona scommessa, a seconda dei casi d'uso.


5
Le interfacce non consentono implementazioni di funzioni concrete, quindi non sono utili qui.
Alex Weinstein

1
Le interfacce supportano l'ereditarietà multipla, a differenza delle classi.
Craig Lewis

-1

Che ne dici di una lezione di invito proprio sotto la classe Messaggio?

quindi la gerarchia va:

Messaggio
--- Invito
------ TextMessage
------ EmailMessage

E nella classe Invitation, aggiungi la funzionalità che era in InvitationTextMessage e InvitationEmailMessage.

So che l'invito non è davvero un tipo di messaggio, è più una funzionalità del messaggio. Quindi non sono sicuro che questo sia un buon design OO o meno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.