J'ai essayé d'étudier PHP récemment, et je me retrouve accroché à des traits. Je comprends le concept de réutilisation horizontale du code et je ne veux pas nécessairement hériter d'une classe abstraite. Ce que je ne comprends pas, c'est: quelle est la différence cruciale entre l'utilisation de traits et d'interfaces?
J'ai essayé de rechercher un article de blog ou un article décent expliquant quand utiliser l'un ou l'autre, mais les exemples que j'ai trouvés jusqu'à présent semblent si similaires qu'ils sont identiques.
Imagick
objets, moins tout le ballonnement nécessaire autrefois avant les traits.Réponses:
Une interface définit un ensemble de méthodes que la classe d' implémentation doit implémenter.
Lorsqu'un trait est
use
présent, les implémentations des méthodes se produisent également - ce qui ne se produit pas dans unInterface
.C'est la plus grande différence.
De la réutilisation horizontale pour PHP RFC :
la source
Message d'intéret public:
Je tiens à préciser pour le compte rendu que je crois que les traits sont presque toujours une odeur de code et devraient être évités en faveur de la composition. À mon avis, l'héritage unique est fréquemment abusé au point d'être un anti-modèle et l'héritage multiple ne fait qu'aggraver ce problème. Vous serez bien mieux servi dans la plupart des cas en privilégiant la composition à l'héritage (qu'il soit unique ou multiple). Si vous êtes toujours intéressé par les traits et leur relation avec les interfaces, lisez la suite ...
Commençons par dire ceci:
Pour écrire du code OO, vous devez comprendre que la POO concerne vraiment les capacités de vos objets. Vous devez penser aux cours en termes de ce qu'ils peuvent faire au lieu de ce qu'ils font réellement . C'est en contraste frappant avec la programmation procédurale traditionnelle où l'accent est mis sur le fait de faire un peu de code "faire quelque chose".
Si le code POO concerne la planification et la conception, une interface est le plan directeur et un objet est la maison entièrement construite. Pendant ce temps, les traits sont simplement un moyen d'aider à construire la maison définie par le plan (l'interface).
Interfaces
Alors, pourquoi devrions-nous utiliser des interfaces? Tout simplement, les interfaces rendent notre code moins fragile. Si vous doutez de cette affirmation, demandez à quiconque a été contraint de conserver un code hérité qui n'a pas été écrit sur les interfaces.
L'interface est un contrat entre le programmeur et son code. L'interface dit: "Tant que vous respectez mes règles, vous pouvez m'implémenter comme bon vous semble et je vous promets de ne pas casser votre autre code."
Donc, à titre d'exemple, considérons un scénario du monde réel (pas de voitures ou de widgets):
Vous commencez par écrire une classe pour mettre en cache les réponses aux demandes à l'aide d'APC:
Ensuite, dans votre objet de réponse HTTP, vous recherchez un hit de cache avant de faire tout le travail pour générer la réponse réelle:
Cette approche fonctionne très bien. Mais peut-être quelques semaines plus tard, vous décidez que vous souhaitez utiliser un système de cache basé sur des fichiers au lieu d'APC. Vous devez maintenant modifier le code de votre contrôleur car vous avez programmé votre contrôleur pour qu'il fonctionne avec les fonctionnalités de la
ApcCacher
classe plutôt que vers une interface qui exprime les capacités de laApcCacher
classe. Disons qu'au lieu de ce qui précède, vous avez faitController
dépendre la classe d'unCacherInterface
au lieu du concretApcCacher
comme ceci:Pour aller avec cela, vous définissez votre interface comme suit:
À votre tour, vous
ApcCacher
et votre nouvelleFileCacher
classe implémentez laCacherInterface
et vous programmez votreController
classe pour utiliser les capacités requises par l'interface.Cet exemple (espérons-le) montre comment la programmation d'une interface vous permet de modifier l'implémentation interne de vos classes sans vous soucier si les modifications vont casser votre autre code.
Traits
Les traits, d'autre part, sont simplement une méthode pour réutiliser le code. Les interfaces ne doivent pas être considérées comme une alternative mutuellement exclusive aux traits. En fait, la création de traits qui remplissent les capacités requises par une interface est le cas d'utilisation idéal .
Vous ne devez utiliser des traits que lorsque plusieurs classes partagent les mêmes fonctionnalités (probablement dictées par la même interface). Cela n'a aucun sens d'utiliser un trait pour fournir des fonctionnalités à une seule classe: cela ne fait qu'obscurcir ce que fait la classe et une meilleure conception déplacerait la fonctionnalité du trait dans la classe appropriée.
Considérez l'implémentation de trait suivante:
Un exemple plus concret: imaginez que les vôtres
FileCacher
et les vôtresApcCacher
de la discussion sur l'interface utilisent la même méthode pour déterminer si une entrée de cache est périmée et doit être supprimée (ce n'est évidemment pas le cas dans la vraie vie, mais allez-y). Vous pouvez écrire un trait et autoriser les deux classes à l'utiliser pour l'exigence d'interface commune.Un dernier mot de prudence: attention à ne pas aller trop loin avec les traits. Souvent, les traits sont utilisés comme béquille pour une mauvaise conception lorsque des implémentations de classe uniques suffisent. Vous devez limiter les traits à remplir les exigences d'interface pour la meilleure conception de code.
la source
A
trait
est essentiellement l'implémentation de PHP par amixin
, et est en fait un ensemble de méthodes d'extension qui peuvent être ajoutées à n'importe quelle classe grâce à l'ajout detrait
. Les méthodes font alors partie de l'implémentation de cette classe, mais sans utiliser d'héritage .Du manuel PHP (accent sur le mien):
Un exemple:
Avec le trait ci-dessus défini, je peux maintenant faire ce qui suit:
À ce stade, lorsque je crée une instance de classe
MyClass
, elle a deux méthodes, appeléesfoo()
etbar()
- qui proviennent demyTrait
. Et - notez que lestrait
méthodes -defined ont déjà un corps de méthode - ce qu'uneInterface
méthode -defined ne peut pas.De plus - PHP, comme de nombreux autres langages, utilise un seul modèle d'héritage - ce qui signifie qu'une classe peut dériver de plusieurs interfaces, mais pas de plusieurs classes. Cependant, une classe PHP peut avoir plusieurs
trait
inclusions - ce qui permet au programmeur d'inclure des pièces réutilisables - comme elles le pourraient si elles incluaient plusieurs classes de base.Quelques points à noter:
Polymorphisme:
Dans l'exemple précédent, où
MyClass
étendSomeBaseClass
,MyClass
est une instance deSomeBaseClass
. En d'autres termes, un tableau tel queSomeBaseClass[] bases
peut contenir des instances deMyClass
. De même, s'il estMyClass
étenduIBaseInterface
, un tableau deIBaseInterface[] bases
pourrait contenir des instances deMyClass
. Il n'y a pas une telle construction polymorphe disponible avec atrait
- car atrait
est essentiellement juste du code qui est copié pour la commodité du programmeur dans chaque classe qui l'utilise.Priorité:
Comme décrit dans le manuel:
Alors, considérez le scénario suivant:
Lors de la création d'une instance de MyClass, ci-dessus, les événements suivants se produisent:
Interface
IBase
requiert une fonction sans paramètre appeléeSomeMethod()
à fournir.BaseClass
fournit une implémentation de cette méthode - satisfaisant le besoin.trait
myTrait
fournit une fonction sans paramètre appeléeSomeMethod()
également, qui a priorité sur laBaseClass
versionclass
MyClass
fournit sa propre version deSomeMethod()
- qui a priorité sur latrait
-version.Conclusion
Interface
ne peut pas fournir une implémentation par défaut d'un corps de méthode, tandis qu'untrait
peut.Interface
est un polymorphe , hérité de construction - tandis qu'untrait
non.Interface
s peuvent être utilisés dans la même classe, ainsi que plusieurstrait
s.la source
mixin
- et comme j'ai revu l'ouverture de ma réponse, j'ai mis à jour pour refléter cela. Merci d'avoir commenté, @BoltClock!Je pense qu'il
traits
est utile de créer des classes qui contiennent des méthodes qui peuvent être utilisées comme méthodes de plusieurs classes différentes.Par exemple:
Vous pouvez avoir et utiliser cette méthode "error" dans n'importe quelle classe qui utilise ce trait.
Pendant que
interfaces
vous êtes avec, vous ne pouvez déclarer que la signature de la méthode, mais pas le code de ses fonctions. De plus, pour utiliser une interface, vous devez suivre une hiérarchie en utilisantimplements
. Ce n'est pas le cas des traits.C'est complètement différent!
la source
to_integer
serait plus probablement inclus dans uneIntegerCast
interface car il n'y a aucun moyen fondamentalement similaire de convertir (intelligemment) des classes en un entier.use Toolkit
vous, vous pourriez$this->toolkit = new Toolkit();
ou est-ce que je manque un avantage du trait lui-même?Something
conteneur que vous faitesif(!$something->do_something('foo')) var_dump($something->errors);
Pour les débutants, la réponse ci-dessus peut être difficile, c'est la façon la plus simple de la comprendre:
Traits
donc si vous voulez avoir une
sayHello
fonction dans d'autres classes sans recréer la fonction entière, vous pouvez utiliser des traits,Cool à droite!
Non seulement les fonctions, vous pouvez utiliser n'importe quoi dans le trait (fonction, variables, const ..). vous pouvez également utiliser plusieurs traits:
use SayWorld,AnotherTraits;
Interface
voici donc comment l'interface est différente des traits: vous devez tout recréer dans l'interface dans la classe implémentée. l'interface n'a pas d'implémentation. et l'interface ne peut avoir que des fonctions et const, elle ne peut pas avoir de variables.
J'espère que ça aide!
la source
C'est une bonne façon d'y penser dans la plupart des circonstances, mais il existe un certain nombre de différences subtiles entre les deux.
Pour commencer, l'
instanceof
opérateur ne fonctionnera pas avec des traits (c.-à-d., Un trait n'est pas un objet réel), donc vous ne pouvez pas nous voir pour voir si une classe a un certain trait (ou pour voir si deux classes autrement non liées partagent un trait ). C'est ce qu'ils entendent par «construction de réutilisation horizontale de code».Il y a maintenant des fonctions en PHP qui vous permettront d'obtenir une liste de tous les traits qu'une classe utilise, mais l'héritage des traits signifie que vous devrez faire des vérifications récursives pour vérifier de manière fiable si une classe à un moment donné a un trait spécifique (il y a un exemple code sur les pages doco PHP). Mais oui, ce n'est certainement pas aussi simple et propre que l'est instanceof, et à mon humble avis, c'est une fonctionnalité qui améliorerait PHP.
De plus, les classes abstraites sont toujours des classes, donc elles ne résolvent pas les problèmes de réutilisation de code liés à l'héritage multiple. N'oubliez pas que vous ne pouvez étendre qu'une seule classe (réelle ou abstraite) mais implémenter plusieurs interfaces.
J'ai trouvé que les traits et les interfaces sont vraiment bons à utiliser main dans la main pour créer un héritage pseudo multiple. Par exemple:
Cela signifie que vous pouvez utiliser instanceof pour déterminer si l'objet Door particulier est Keyed ou non, vous savez que vous obtiendrez un ensemble cohérent de méthodes, etc., et tout le code est au même endroit dans toutes les classes qui utilisent KeyedTrait.
la source
Les traits sont simplement pour la réutilisation du code .
L'interface fournit simplement la signature des fonctions à définir dans la classe où elle peut être utilisée en fonction de la discrétion du programmeur . Nous donnant ainsi un prototype pour un groupe de classes .
Pour référence- http://www.php.net/manual/en/language.oop5.traits.php
la source
Vous pouvez considérer un trait comme un "copier-coller" automatisé de code, en gros.
L'utilisation de traits est dangereuse car il n'y a aucun moyen de savoir ce qu'elle fait avant l'exécution.
Cependant, les traits sont plus flexibles en raison de leur manque de limitations telles que l'héritage.
Les traits peuvent être utiles pour injecter une méthode qui vérifie quelque chose dans une classe, par exemple, l'existence d'une autre méthode ou attribut. Un bel article là-dessus (mais en français, désolé) .
Pour les lecteurs français qui peuvent l'obtenir, le magazine GNU / Linux HS 54 a un article sur ce sujet.
la source
Si vous connaissez l'anglais et savez ce que cela
trait
signifie, c'est exactement ce que dit son nom. Il s'agit d'un ensemble de méthodes et de propriétés sans classe que vous associez aux classes existantes en tapantuse
.Fondamentalement, vous pouvez le comparer à une seule variable. Les fonctions de fermeture peuvent
use
ces variables de l'extérieur de la portée et de cette façon, ils ont la valeur à l'intérieur. Ils sont puissants et peuvent être utilisés dans tout. Il en va de même pour les traits s'ils sont utilisés.la source
D'autres réponses ont fait un excellent travail pour expliquer les différences entre les interfaces et les traits. Je me concentrerai sur un exemple utile du monde réel, en particulier celui qui démontre que les traits peuvent utiliser des variables d'instance - vous permettant d'ajouter un comportement à une classe avec un code passe-partout minimal.
Encore une fois, comme mentionné par d'autres, les traits s'associent bien avec les interfaces, permettant à l'interface de spécifier le contrat de comportement et le trait de remplir l'implémentation.
L'ajout de capacités de publication / abonnement d'événement à une classe peut être un scénario courant dans certaines bases de code. Il existe 3 solutions communes:
use
le trait, alias l'importer, pour gagner les capacités.Dans quelle mesure chacun fonctionne-t-il?
# 1 Ne fonctionne pas bien. Ce serait le cas, jusqu'au jour où vous vous rendez compte que vous ne pouvez pas étendre la classe de base parce que vous étendez déjà autre chose. Je ne montrerai pas d'exemple de cela, car il devrait être évident à quel point il est limité d'utiliser l'héritage comme celui-ci.
# 2 & # 3 fonctionnent bien. Je vais montrer un exemple qui met en évidence certaines différences.
Tout d'abord, du code qui sera le même entre les deux exemples:
Une interface
Et du code pour démontrer l'utilisation:
Ok, permet maintenant de montrer comment l'implémentation de la
Auction
classe différera lors de l'utilisation des traits.Tout d'abord, voici à quoi ressemblerait le # 2 (en utilisant la composition):
Voici à quoi ressemblerait le # 3 (traits):
Notez que le code à l'intérieur de la
EventEmitterTrait
est exactement le même que celui de laEventEmitter
classe, sauf que le trait déclare latriggerEvent()
méthode protégée. Ainsi, la seule différence que vous devez considérer est l'implémentation de laAuction
classe .Et la différence est grande. Lorsque nous utilisons la composition, nous obtenons une excellente solution, nous permettant de réutiliser notre
EventEmitter
par autant de classes que nous le souhaitons. Mais, le principal inconvénient est que nous avons beaucoup de code passe-partout que nous devons écrire et maintenir, car pour chaque méthode définie dans l'Observable
interface, nous devons l'implémenter et écrire un code passe-partout ennuyeux qui transmet simplement les arguments à la méthode correspondante dans notre composé l'EventEmitter
objet. L'utilisation de la caractéristique dans cet exemple nous permet d'éviter cela , ce qui nous aide à réduire le code standard et à améliorer la maintenabilité .Cependant, il peut y avoir des moments où vous ne voulez pas que votre
Auction
classe implémente l'Observable
interface complète - peut-être que vous voulez seulement exposer 1 ou 2 méthodes, ou peut-être même aucune pour que vous puissiez définir vos propres signatures de méthode. Dans un tel cas, vous préférerez peut-être toujours la méthode de composition.Mais, le trait est très convaincant dans la plupart des scénarios, surtout si l'interface a beaucoup de méthodes, ce qui vous oblige à écrire beaucoup de passe-partout.
* Vous pouvez en fait faire les deux - définir la
EventEmitter
classe au cas où vous voudriez l'utiliser de manière compositionnelle, et définir leEventEmitterTrait
trait aussi, en utilisant l'EventEmitter
implémentation de classe à l'intérieur du trait :)la source
Le trait est le même qu'une classe que nous pouvons utiliser à des fins d'héritage multiples et également de réutilisation de code.
Nous pouvons utiliser des traits à l'intérieur de la classe et nous pouvons également utiliser plusieurs traits dans la même classe avec «use keyword».
L'interface utilise pour la réutilisation du code comme un trait
l'interface est d'étendre plusieurs interfaces afin que nous puissions résoudre les multiples problèmes d'héritage, mais lorsque nous implémentons l'interface, nous devons créer toutes les méthodes à l'intérieur de la classe. Pour plus d'informations, cliquez sur le lien ci-dessous:
http://php.net/manual/en/language.oop5.traits.php http://php.net/manual/en/language.oop5.interfaces.php
la source
Une interface est un contrat qui dit que «cet objet est capable de faire cette chose», tandis qu'un trait donne à l'objet la capacité de faire la chose.
Un trait est essentiellement un moyen de «copier-coller» du code entre les classes.
Essayez de lire cet article, Quels sont les traits PHP?
la source
La principale différence est que, avec les interfaces, vous devez définir l'implémentation réelle de chaque méthode au sein de chaque classe qui implémente ladite interface, de sorte que plusieurs classes peuvent implémenter la même interface mais avec un comportement différent, tandis que les traits ne sont que des morceaux de code injectés dans une classe; une autre différence importante est que les méthodes de trait ne peuvent être que des méthodes de classe ou des méthodes statiques, contrairement aux méthodes d'interface qui peuvent également (et sont généralement) des méthodes d'instance.
la source