Héritage multiple en PHP

97

Je recherche un moyen efficace et propre de contourner le fait que PHP5 ne prend toujours pas en charge l'héritage multiple. Voici la hiérarchie des classes:

Message
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

Les deux types de classes Invitation * ont beaucoup en commun; J'adorerais avoir une classe parentale commune, Invitation, dont ils hériteraient tous les deux. Malheureusement, ils ont aussi beaucoup en commun avec leurs ancêtres actuels ... TextMessage et EmailMessage. Désir classique d'héritage multiple ici.

Quelle est l'approche la plus légère pour résoudre le problème?

Merci!

Alex Weinstein
la source
4
Il n'y a pas beaucoup de cas dans lesquels l'héritage (ou même l'héritage multiple) est justifiable. Regardez les principes SOLIDES. Préférez la composition à l'héritage.
Ondřej Mirtes
2
@ OndřejMirtes que voulez-vous dire - "pas beaucoup de cas dans lesquels l'héritage est justifiable."?
styler1972
12
Je veux dire - l'héritage apporte plus de problèmes que d'avantages (regardez le principe de substitution de Liskov). Vous pouvez résoudre presque tout avec la composition et éviter beaucoup de maux de tête. L'héritage est également statique - cela signifie que vous ne pouvez pas modifier ce qui est déjà écrit dans le code. Mais la composition peut être utilisée à l'exécution et vous pouvez choisir des implémentations de manière dynamique - par exemple, réutiliser la même classe avec différents mécanismes de mise en cache.
Ondřej Mirtes
5
PHP 5.4 a des "traits": stackoverflow.com/a/13966131/492130
f.ardelian
1
Je suggérerais aux débutants de ne jamais utiliser l'héritage . En général, les deux seules situations dans lesquelles l'héritage est autorisé sont: 1) lors de la construction d'une bibliothèque, afin que les utilisateurs écrivent moins de code et 2) lorsque le responsable du projet vous demande de l'utiliser
gurghet

Réponses:

141

Alex, la plupart du temps, vous avez besoin d'héritage multiple, c'est un signal que la structure de votre objet est quelque peu incorrecte. Dans la situation que vous avez décrite, je vois que vous avez une responsabilité de classe tout simplement trop large. Si Message fait partie du modèle commercial de l'application, il ne doit pas se soucier du rendu de la sortie. Au lieu de cela, vous pouvez diviser la responsabilité et utiliser MessageDispatcher qui envoie le message transmis à l'aide du backend texte ou html. Je ne connais pas votre code, mais laissez-moi le simuler de cette façon:

$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 cette façon, vous pouvez ajouter une spécialisation à la 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);

Notez que MessageDispatcher prendrait la décision d'envoyer au format HTML ou texte brut en fonction de la typepropriété de l'objet Message transmis.

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

Pour résumer, la responsabilité est partagée entre deux classes. La configuration des messages est effectuée dans la classe InvitationHTMLMessage / InvitationTextMessage et l'algorithme d'envoi est délégué au répartiteur. C'est ce qu'on appelle le modèle de stratégie, vous pouvez en savoir plus ici .

Michał Rudnicki
la source
13
Réponse étonnamment étendue, merci! J'ai appris quelque chose aujourd'hui!
Alex Weinstein le
26
... Je sais que c'est un peu vieux (je cherchais à voir si PHP avait MI ... juste pour la curiosité) Je ne pense pas que ce soit un bon exemple du modèle de stratégie. Le modèle de stratégie est conçu pour que vous puissiez mettre en œuvre une nouvelle «stratégie» à tout moment. L'implémentation que vous avez fournie ne présente pas une telle capacité. Au lieu de cela, Message devrait avoir la fonction "send" qui appelle MessageDispatcher-> dispatch () (Dispatcher soit un paramètre soit un membre var), et les nouvelles classes HTMLDispatcher & TextDispatcher implémenteront "dispatch" de leurs manières respectives (cela permet aux autres Dispatchers de faire autre œuvre)
Terence Honles
12
Malheureusement, PHP n'est pas idéal pour implémenter un modèle de stratégie. Les langages qui prennent en charge la surcharge de méthodes fonctionnent mieux ici - imaginez que vous ayez deux méthodes du même nom: dispatch (HTMLMessage $ m) et dispatch (TextMessage $) - maintenant dans un langage fortement typé, le compilateur / interpréteur emploierait automatiquement la bonne "stratégie" basée sur type de paramètre. En dehors de cela, je ne pense pas qu'être ouvert à la mise en œuvre d'une nouvelle stratégie soit l'essence même du modèle de stratégie. Bien sûr, c'est une bonne chose à avoir, mais souvent pas une exigence.
Michał Rudnicki
2
Supposons que vous ayez une classe Tracing(ce n'est qu'un exemple) où vous voulez avoir des choses génériques comme le débogage dans un fichier, envoyer des SMS pour un problème critique et ainsi de suite. Toutes vos classes sont des enfants de cette classe. Supposons maintenant que vous souhaitiez créer une classe Exceptionqui devrait avoir ces fonctions (= enfant de Tracing). Cette classe doit être un enfant de Exception. Comment concevoir de telles choses sans héritage multiple? Oui, vous pouvez toujours avoir une solution, mais vous serez toujours proche du piratage. Et le piratage = solution coûteuse à long terme. Fin de l'histoire.
Olivier Pons
1
Olivier Pons, je ne pense pas que le sous-classement de Tracing serait la bonne solution pour votre cas d'utilisation. Quelque chose d'aussi simple que d'avoir une classe Tracing abstraite avec des méthodes statiques Debug, SendSMS, etc., qui peut ensuite être appelée à partir de n'importe quelle autre classe avec Tracing :: SendSMS () etc. Vos autres classes ne sont pas des «types de» de Tracing, ils «utilisent» le traçage. Remarque: certaines personnes peuvent préférer un singleton aux méthodes statiques; Je préfère utiliser des méthodes statiques sur des singletons lorsque cela est possible.
15

Peut-être pouvez-vous remplacer une relation «est-un» par une relation «a-une»? Une invitation peut avoir un message, mais elle n'a pas nécessairement besoin de «est-un» message. Une invitation fe peut être confirmée, ce qui ne va pas bien avec le modèle Message.

Recherchez «composition vs héritage» si vous avez besoin d'en savoir plus à ce sujet.

Matthias Kestenholz
la source
9

Si je peux citer Phil dans ce fil ...

PHP, comme Java, ne prend pas en charge l'héritage multiple.

Venir en PHP 5.4 sera des traits tenteront de fournir une solution à ce problème.

En attendant, vous feriez mieux de repenser la conception de votre classe. Vous pouvez implémenter plusieurs interfaces si vous recherchez une API étendue pour vos classes.

Et Chris ...

PHP ne prend pas vraiment en charge l'héritage multiple, mais il existe des moyens (quelque peu compliqués) de l'implémenter. Consultez cette URL pour quelques exemples:

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

Je pensais qu'ils avaient tous les deux des liens utiles. J'ai hâte d'essayer des traits ou peut-être des mixins ...

Simon Est
la source
1
Les traits sont la voie à suivre
Jonathan
6

Le framework Symfony a un plugin mixin pour cela , vous voudrez peut-être le vérifier - même juste pour des idées, sinon pour l'utiliser.

La réponse du «modèle de conception» est d'abstraire la fonctionnalité partagée dans un composant séparé et de la composer au moment de l'exécution. Pensez à un moyen d'abstraire la fonctionnalité Invitation en tant que classe qui est associée à vos classes Message d'une manière autre que l'héritage.

Joelhardi
la source
4

J'utilise les traits de PHP 5.4 pour résoudre ce problème. http://php.net/manual/en/language.oop5.traits.php

Cela permet un héritage classique avec extend, mais donne également la possibilité de placer des fonctionnalités et des propriétés communes dans un «trait». Comme le dit le manuel:

Traits est un mécanisme de réutilisation de code dans des langages d'héritage uniques tels que PHP. Un trait est destiné à réduire certaines limitations de l'héritage unique en permettant à un développeur de réutiliser librement des ensembles de méthodes dans plusieurs classes indépendantes vivant dans des hiérarchies de classes différentes.

MatthewPearson
la source
3

On dirait que le motif du décorateur peut convenir, mais difficile à dire sans plus de détails.

Danio
la source
Meilleure réponse IMO.
l00k
3

C'est à la fois une question et une solution ...

Qu'en est-il de la magie _ appel (),Méthodes _get (), __set ()? Je n'ai pas encore testé cette solution mais que faire si vous créez une classe multiInherit. Une variable protégée dans une classe enfant peut contenir un tableau de classes à hériter. Le constructeur de la classe multi-interface peut créer des instances de chacune des classes héritées et les lier à une propriété privée, par exemple _ext. La méthode __call () pourrait utiliser la fonction method_exists () sur chacune des classes du tableau _ext pour localiser la méthode correcte à appeler. __get () et __set pourraient être utilisés pour localiser les propriétés internes, ou si vous êtes un expert avec des références, vous pouvez faire des propriétés de la classe enfant et des classes héritées des références aux mêmes données. L'héritage multiple de votre objet serait transparent pour le code utilisant ces objets. Aussi, les objets internes peuvent accéder directement aux objets hérités si nécessaire tant que le tableau _ext est indexé par nom de classe. J'ai envisagé de créer cette super-classe et je ne l'ai pas encore implémentée car je pense que si cela fonctionne, cela pourrait conduire à développer de mauvaises habitudes de programmation.

Ralph Ritoch
la source
Je pense que c'est faisable. Il combinera les fonctionnalités de plusieurs classes, mais n'en héritera pas réellement (dans le sens de instanceof)
user102008
Et cela échouera sûrement à autoriser les remplacements dès que la classe interne fera un appel à self :: <whatever>
Phil Lello
1

J'ai quelques questions à poser pour clarifier ce que vous faites:

1) Votre objet de message contient-il simplement un message, par exemple le corps, le destinataire, l'heure programmée? 2) Que comptez-vous faire avec votre objet Invitation? Doit-il être traité spécialement par rapport à un EmailMessage? 3) Dans l'affirmative, QUELLE EST LA particularité? 4) Si tel est le cas, pourquoi les types de messages doivent-ils être traités différemment pour une invitation? 5) Que faire si vous souhaitez envoyer un message de bienvenue ou un message OK? Sont-ils aussi de nouveaux objets?

Il semble que vous essayez de combiner trop de fonctionnalités dans un ensemble d'objets qui ne devraient être concernés que par la conservation du contenu d'un message - et non par la façon dont il devrait être géré. Pour moi, vous voyez, il n'y a aucune différence entre une invitation ou un message standard. Si l'invitation nécessite un traitement spécial, cela signifie une logique d'application et non un type de message.

Par exemple: un système que j'ai construit avait un objet de message de base partagé qui a été étendu en SMS, e-mail et autres types de messages. Cependant: ceux-ci n'ont pas été étendus davantage - un message d'invitation était simplement un texte prédéfini à envoyer via un message de type Email. Une demande d'invitation spécifique serait concernée par la validation et d'autres exigences pour une invitation. Après tout, tout ce que vous voulez faire est d'envoyer le message X au destinataire Y qui devrait être un système discret à part entière.


la source
0

Même problème que Java. Essayez d'utiliser des interfaces avec des fonctions abstraites pour résoudre ce problème

DeeCee
la source
0

PHP prend en charge les interfaces. Cela pourrait être un bon pari, en fonction de vos cas d'utilisation.

Cheekysoft
la source
5
Les interfaces ne permettent pas d'implémentations de fonctions concrètes, elles ne sont donc pas utiles ici.
Alex Weinstein
1
Les interfaces prennent en charge l'héritage multiple, contrairement aux classes.
Craig Lewis
-1

Que diriez-vous d'une classe Invitation juste en dessous de la classe Message?

donc la hiérarchie va:

Message
--- Invitation
------ TextMessage
------ EmailMessage

Et dans la classe Invitation, ajoutez la fonctionnalité qui était dans InvitationTextMessage et InvitationEmailMessage.

Je sais que l'invitation n'est pas vraiment un type de message, c'est plutôt une fonctionnalité de message. Je ne suis donc pas sûr que ce soit une bonne conception OO ou non.

nube
la source