Herencia múltiple en PHP

97

Estoy buscando una forma buena y limpia de evitar el hecho de que PHP5 todavía no admite la herencia múltiple. Aquí está la jerarquía de clases:

Mensaje
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

Los dos tipos de clases de Invitación * tienen mucho en común; Me encantaría tener una clase de padres común, Invitación, de la que ambos heredarían. Desafortunadamente, también tienen mucho en común con sus antepasados ​​actuales ... TextMessage y EmailMessage. Deseo clásico de herencia múltiple aquí.

¿Cuál es el enfoque más ligero para resolver el problema?

¡Gracias!

Alex Weinstein
fuente
4
No hay muchos casos en los que la herencia (o incluso la herencia múltiple) sea justificable. Mira los principios SOLID. Prefiere la composición a la herencia.
Ondřej Mirtes
2
@ OndřejMirtes ¿a qué te refieres con "no hay muchos casos en los que la herencia sea justificable"?
styler1972
12
Quiero decir, la herencia trae más problemas que beneficios (observe el principio de sustitución de Liskov). Puedes resolver casi todo con la composición y ahorrarte muchos dolores de cabeza. La herencia también es estática, lo que significa que no puede cambiar lo que ya está escrito en el código. Pero la composición se puede usar en tiempo de ejecución y puede elegir implementaciones dinámicamente, por ejemplo, reutilizar la misma clase con diferentes mecanismos de almacenamiento en caché.
Ondřej Mirtes
5
PHP 5.4 tiene "rasgos": stackoverflow.com/a/13966131/492130
f.ardelian
1
Sugeriría a los principiantes que nunca usen la herencia . En general, las únicas dos situaciones en las que se permite la herencia son: 1) cuando se construye una biblioteca, por lo que los usuarios escriben menos código y 2) cuando el líder del proyecto exige que lo use
gurghet

Respuestas:

141

Alex, la mayoría de las veces que necesitas herencia múltiple es una señal de que la estructura de tu objeto es algo incorrecta. En la situación que describiste, veo que tienes una responsabilidad de clase simplemente demasiado amplia. Si Message es parte del modelo de negocio de la aplicación, no debería preocuparse por la presentación de resultados. En su lugar, puede dividir la responsabilidad y usar MessageDispatcher que envía el mensaje transmitido usando texto o backend html. No conozco tu código, pero déjame simularlo de esta manera:

$m = new Message();
$m->type = 'text/html';
$m->from = 'John Doe <[email protected]>';
$m->to = 'Random Hacker <[email protected]>';
$m->subject = 'Invitation email';
$m->importBody('invitation.html');

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

De esta manera puede agregar algo de especialización a la clase de mensaje:

$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);

Tenga en cuenta que MessageDispatcher tomaría la decisión de enviar como HTML o texto sin formato dependiendo de la typepropiedad en el objeto Message que se haya pasado.

// 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");
    }
}

En resumen, la responsabilidad se divide en dos clases. La configuración del mensaje se realiza en la clase InvitationHTMLMessage / InvitationTextMessage y el algoritmo de envío se delega al despachador. Esto se llama Patrón de estrategia, puede leer más sobre él aquí .

Michał Rudnicki
fuente
13
Respuesta sorprendentemente extensa, ¡gracias! ¡Aprendí algo hoy!
Alex Weinstein
26
... Sé que esto es un poco antiguo (estaba buscando para ver si PHP tenía MI ... solo por curiosidad) No creo que este sea un buen ejemplo del Patrón de estrategia. El patrón de estrategia está diseñado para que pueda implementar una nueva "estrategia" en cualquier momento. La implementación que ha proporcionado no presenta tal capacidad. En su lugar, Message debe tener la función "enviar" que llama a MessageDispatcher-> dispatch () (Dispatcher ya sea param o miembro var), y las nuevas clases HTMLDispatcher y TextDispatcher implementarán "dispatch" en sus respectivas formas (esto permite que otros Dispatchers lo hagan otro trabajo)
Terence Honles
12
Desafortunadamente, PHP no es ideal para implementar el patrón de estrategia. Los lenguajes que admiten la sobrecarga de métodos funcionan mejor aquí; imagina que tienes dos métodos con el mismo nombre: despacho (HTMLMessage $ m) y despacho (TextMessage $); ahora en un lenguaje fuertemente tipado, el compilador / intérprete emplearía automáticamente la "estrategia" correcta basada en tipo de parámetro. Aparte de eso, no creo que estar abierto a la implementación de nuevas estrategias sea la esencia de Strategy Pattern. Seguro que es bueno tenerlo, pero a menudo no es un requisito.
Michał Rudnicki
2
Suponga que tiene una clase Tracing(esto es solo una muestra) donde desea tener cosas genéricas como depurar en un archivo, enviar SMS para problemas críticos, etc. Todas sus clases son hijos de esta clase. Ahora suponga que desea crear una clase Exceptionque debería tener esas funciones (= hijo de Tracing). Esta clase debe ser hija de Exception. ¿Cómo se diseña ese material sin herencia múltiple? Sí, siempre puede tener una solución, pero siempre estará cerca de piratear. Y piratería = solución costosa a largo plazo. Fin de la historia.
Olivier Pons
1
Olivier Pons, no creo que el seguimiento de subclases sea la solución correcta para su caso de uso. Algo tan simple como tener una clase de rastreo abstracta con métodos estáticos Debug, SendSMS, etc., que luego se puede llamar desde cualquier otra clase con Tracing :: SendSMS () etc. Sus otras clases no son 'tipos de' de rastreo, ellos 'usan' el rastreo. Nota: algunas personas pueden preferir métodos singleton sobre estáticos; Prefiero usar métodos estáticos sobre singleton cuando sea posible.
15

¿Quizás pueda reemplazar una relación 'es-a' con una relación 'tiene-a'? Una invitación puede tener un mensaje, pero no necesariamente tiene que ser un mensaje "es-un". Es posible que se confirme una invitación fe, lo que no va bien con el modelo Mensaje.

Busque 'composición versus herencia' si necesita saber más sobre eso.

Matthias Kestenholz
fuente
9

Si puedo citar a Phil en este hilo ...

PHP, como Java, no admite herencia múltiple.

En PHP 5.4 habrá rasgos que intentarán proporcionar una solución a este problema.

Mientras tanto, sería mejor que reconsiderara el diseño de su clase. Puede implementar múltiples interfaces si busca una API extendida para sus clases.

Y Chris ...

PHP realmente no admite herencia múltiple, pero hay algunas formas (algo complicadas) de implementarlo. Consulte esta URL para ver algunos ejemplos:

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

Pensé que ambos tenían enlaces útiles. No puedo esperar para probar rasgos o tal vez algunos mixins ...

Simon East
fuente
1
Los rasgos son el camino a seguir
Jonathan
6

El marco de Symfony tiene un complemento de mezcla para esto , es posible que desee verificarlo, incluso solo para obtener ideas, si no para usarlo.

La respuesta del "patrón de diseño" es abstraer la funcionalidad compartida en un componente separado y componer en tiempo de ejecución. Piense en una forma de abstraer la funcionalidad de Invitación como una clase que se asocia con sus clases de Mensaje de alguna manera que no sea la herencia.

joelhardi
fuente
4

Estoy usando rasgos en PHP 5.4 como forma de resolver esto. http://php.net/manual/en/language.oop5.traits.php

Esto permite la herencia clásica con extensiones, pero también ofrece la posibilidad de colocar funciones y propiedades comunes en un "rasgo". Como dice el manual:

Traits es un mecanismo para la reutilización de código en lenguajes de herencia única como PHP. Un rasgo está destinado a reducir algunas limitaciones de la herencia única al permitir que un desarrollador reutilice conjuntos de métodos libremente en varias clases independientes que viven en diferentes jerarquías de clases.

MatthewPearson
fuente
3

Parece que el patrón del decorador puede ser adecuado, pero es difícil saberlo sin más detalles.

danio
fuente
Mejor respuesta en mi opinión.
l00k
3

Esto es tanto una pregunta como una solución ...

¿Qué pasa con la llamada mágica _ (),_get (), __set () métodos? Todavía no he probado esta solución, pero ¿qué pasa si crea una clase multiInherit? Una variable protegida en una clase secundaria podría contener una matriz de clases para heredar. El constructor de la clase de interfaz múltiple podría crear instancias de cada una de las clases que se heredan y vincularlas a una propiedad privada, digamos _ext. El método __call () podría usar la función method_exists () en cada una de las clases en la matriz _ext para localizar el método correcto para llamar. __get () y __set podrían usarse para ubicar propiedades internas, o si es un experto con referencias, podría hacer que las propiedades de la clase secundaria y las clases heredadas sean referencias a los mismos datos. La herencia múltiple de su objeto sería transparente para codificar usando esos objetos. También, los objetos internos podrían acceder directamente a los objetos heredados si fuera necesario, siempre que la matriz _ext esté indexada por nombre de clase. He imaginado la creación de esta superclase y aún no la he implementado, ya que creo que si funciona, podría llevar a desarrollar algunos malos hábitos de programación.

Ralph Ritoch
fuente
Creo que esto es factible. Combinará la funcionalidad de varias clases, pero en realidad no las heredará (en el sentido de instanceof)
user102008
Y esto seguramente no permitirá anulaciones tan pronto como en la clase interna se haga una llamada a sí mismo :: <lo que sea>
Phil Lello
1

Tengo un par de preguntas para aclarar lo que está haciendo:

1) ¿Su objeto de mensaje solo contiene un mensaje, por ejemplo, cuerpo, destinatario, horario de programación? 2) ¿Qué piensa hacer con su objeto Invitación? ¿Debe tratarse especialmente en comparación con un mensaje de correo electrónico? 3) Si es así, ¿QUÉ tiene de especial? 4) Si ese es el caso, ¿por qué los tipos de mensajes deben manejarse de manera diferente para una invitación? 5) ¿Qué sucede si desea enviar un mensaje de bienvenida o un mensaje OK? ¿También son objetos nuevos?

Suena como si estuvieras intentando combinar demasiada funcionalidad en un conjunto de objetos que solo deberían preocuparse por mantener el contenido de un mensaje, y no cómo debería manejarse. Para mí, verás, no hay diferencia entre una invitación o un mensaje estándar. Si la invitación requiere un tratamiento especial, eso significa lógica de aplicación y no un tipo de mensaje.

Por ejemplo: un sistema que construí tenía un objeto de mensaje base compartido que se extendió a SMS, correo electrónico y otros tipos de mensajes. Sin embargo, estos no se ampliaron más: un mensaje de invitación era simplemente un texto predefinido que se enviaría a través de un mensaje de tipo Correo electrónico. Una solicitud de invitación específica estaría relacionada con la validación y otros requisitos para una invitación. Después de todo, todo lo que quiere hacer es enviar el mensaje X al destinatario Y, que debería ser un sistema discreto por derecho propio.


fuente
0

Mismo problema que Java. Intente usar interfaces con funciones abstractas para resolver ese problema

DeeCee
fuente
0

PHP admite interfaces. Esta podría ser una buena apuesta, dependiendo de sus casos de uso.

Cheekysoft
fuente
5
Las interfaces no permiten implementaciones de funciones concretas, por lo que no son útiles aquí.
Alex Weinstein
1
Las interfaces admiten la herencia múltiple, a diferencia de las clases.
Craig Lewis
-1

¿Qué tal una clase de invitación justo debajo de la clase de mensaje?

entonces la jerarquía va:

Mensaje
--- Invitación
------ Mensaje de texto
------ Mensaje de correo electrónico

Y en la clase de invitación, agregue la funcionalidad que estaba en InvitationTextMessage y InvitationEmailMessage.

Sé que la invitación no es realmente un tipo de mensaje, es más una funcionalidad de mensaje. Así que no estoy seguro de si este es un buen diseño de OO o no.

nube
fuente