Traits vs interfaces


344

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.


6
l'interface n'a pas de code dans les corps de fonction. ils n'ont en fait aucun organe fonctionnel.
hakre

2
Malgré ma réponse très appréciée, j'aimerais qu'il soit déclaré pour mémoire que je suis généralement anti-trait / mixin . Consultez cette transcription de discussion pour lire comment les traits sapent souvent les bonnes pratiques de POO .
rdlowrey

2
Je dirais le contraire. Ayant travaillé avec PHP pendant des années auparavant et depuis l'avènement des traits, je pense qu'il est facile de prouver leur valeur. Il suffit de lire cet exemple pratique qui permet aux `` modèles d'image '' de marcher et de parler également comme des Imagickobjets, moins tout le ballonnement nécessaire autrefois avant les traits.
quickshiftin

Les traits et l'interface sont similaires. La principale différence est que les Traits vous permettent d'implémenter les méthodes, contrairement à l'Interface.
John

Réponses:


238

Une interface définit un ensemble de méthodes que la classe d' implémentation doit implémenter.

Lorsqu'un trait est useprésent, les implémentations des méthodes se produisent également - ce qui ne se produit pas dans un Interface.

C'est la plus grande différence.

De la réutilisation horizontale pour PHP RFC :

Traits est un mécanisme de réutilisation de code dans des langages à héritage unique 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.


2
@JREAM Dans la pratique, rien. En réalité, bien plus.
Alec Gorge

79
Sauf que les traits ne sont pas du tout des interfaces. Les interfaces sont des spécifications qui peuvent être vérifiées. Les caractères ne peuvent pas être vérifiés, ils ne sont donc que la mise en œuvre. Ils sont exactement l'opposé des interfaces. Cette ligne dans le RFC est tout simplement fausse ...
ircmaxell

195
Les traits sont essentiellement du copier-coller assisté par langue .
Shahid

10
Ce n'est pas une métaphore. C'est boucher le sens d'un mot. C'est comme décrire une boîte comme une surface avec du volume.
cleong

6
Pour développer les commentaires d'ircmaxell et de Shadi: Vous pouvez vérifier si un objet implémente une interface (via instanceof), et vous pouvez vous assurer qu'un argument de méthode implémente une interface via une indication de type dans la signature de méthode. Vous ne pouvez pas effectuer de vérification correspondante des traits.
Brian D'Astous

530

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:

La programmation orientée objet (POO) peut être un paradigme difficile à saisir. Ce n'est pas parce que vous utilisez des classes que votre code est orienté objet (OO).

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 souhaitez implémenter un système de mise en cache pour une application Web afin de réduire la charge du serveur

Vous commencez par écrire une classe pour mettre en cache les réponses aux demandes à l'aide d'APC:

class ApcCacher
{
  public function fetch($key) {
    return apc_fetch($key);
  }
  public function store($key, $data) {
    return apc_store($key, $data);
  }
  public function delete($key) {
    return apc_delete($key);
  }
}

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:

class Controller
{
  protected $req;
  protected $resp;
  protected $cacher;

  public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
    $this->req    = $req;
    $this->resp   = $resp;
    $this->cacher = $cacher;

    $this->buildResponse();
  }

  public function buildResponse() {
    if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
      $this->resp = $response;
    } else {
      // Build the response manually
    }
  }

  public function getResponse() {
    return $this->resp;
  }
}

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 ApcCacherclasse plutôt que vers une interface qui exprime les capacités de la ApcCacherclasse. Disons qu'au lieu de ce qui précède, vous avez fait Controllerdépendre la classe d'un CacherInterfaceau lieu du concret ApcCachercomme ceci:

// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)

Pour aller avec cela, vous définissez votre interface comme suit:

interface CacherInterface
{
  public function fetch($key);
  public function store($key, $data);
  public function delete($key);
}

À votre tour, vous ApcCacheret votre nouvelle FileCacherclasse implémentez la CacherInterfaceet vous programmez votre Controllerclasse 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:

interface Person
{
    public function greet();
    public function eat($food);
}

trait EatingTrait
{
    public function eat($food)
    {
        $this->putInMouth($food);
    }

    private function putInMouth($food)
    {
        // Digest delicious food
    }
}

class NicePerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Good day, good sir!';
    }
}

class MeanPerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Your mother was a hamster!';
    }
}

Un exemple plus concret: imaginez que les vôtres FileCacheret les vôtres ApcCacherde 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.


69
Je cherchais vraiment la réponse simple et rapide qui a été fournie ci-dessus, mais je dois dire que vous avez donné une excellente réponse en profondeur qui aidera à rendre la distinction plus claire pour les autres, bravo.
datguywhowanders

35
"[C] créer des traits qui remplissent les capacités requises par une interface dans une classe donnée est un cas d'utilisation idéal". Exactement: +1
Alec Gorge

5
Serait-il juste de dire que les traits en PHP sont similaires aux mixins dans d'autres langages?
Eno

5
@igorpan À toutes fins utiles, je dirais que la mise en œuvre des traits de PHP est la même que l'héritage multiple. Il convient de noter que si un trait en PHP spécifie des propriétés statiques, chaque classe utilisant le trait aura sa propre copie de la propriété statique. Plus important encore ... en voyant comment ce message est maintenant extrêmement élevé sur les SERP lors de la recherche de traits, je vais ajouter une annonce de service public en haut de la page. Tu devrais le lire.
rdlowrey

3
+1 pour une explication approfondie. Je viens d'un fond rubis, où les mixins sont beaucoup utilisés; juste pour ajouter mes deux cents, une bonne règle empirique que nous utilisons pourrait être traduite en php comme "ne pas implémenter de méthodes qui mutent $ this en traits". Cela empêche tout un tas de sessions de débogage folles ... Un mixin ne doit PAS non plus faire d'hypothèses sur la classe dans laquelle il sera mélangé (ou vous devriez être très clair et réduire les dépendances au strict minimum). À cet égard, je trouve votre idée de traits mettant en œuvre des interfaces agréable.
m_x

67

A traitest essentiellement l'implémentation de PHP par a mixin, et est en fait un ensemble de méthodes d'extension qui peuvent être ajoutées à n'importe quelle classe grâce à l'ajout de trait. 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):

Les traits sont un mécanisme de réutilisation de code dans des langages à héritage unique tels que PHP. ... C'est un ajout à l'héritage traditionnel et permet une composition horizontale du comportement; c'est-à-dire l'application des membres de la classe sans exiger d'héritage.

Un exemple:

trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}

Avec le trait ci-dessus défini, je peux maintenant faire ce qui suit:

class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}

À ce stade, lorsque je crée une instance de classe MyClass, elle a deux méthodes, appelées foo()et bar()- qui proviennent de myTrait. Et - notez que les traitméthodes -defined ont déjà un corps de méthode - ce qu'une Interfacemé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 traitinclusions - 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:

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------

Polymorphisme:

Dans l'exemple précédent, où MyClass étend SomeBaseClass , MyClass est une instance de SomeBaseClass. En d'autres termes, un tableau tel que SomeBaseClass[] basespeut contenir des instances de MyClass. De même, s'il est MyClassétendu IBaseInterface, un tableau de IBaseInterface[] basespourrait contenir des instances de MyClass. Il n'y a pas une telle construction polymorphe disponible avec a trait- car a traitest 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:

Un membre hérité d'une classe de base est remplacé par un membre inséré par un trait. L'ordre de priorité est que les membres de la classe actuelle remplacent les méthodes Trait, qui en retour remplacent les méthodes héritées.

Alors, considérez le scénario suivant:

class BaseClass {
    function SomeMethod() { /* Do stuff here */ }
}

interface IBase {
    function SomeMethod();
}

trait myTrait {
    function SomeMethod() { /* Do different stuff here */ }
}

class MyClass extends BaseClass implements IBase {
    use myTrait;

    function SomeMethod() { /* Do a third thing */ }
}

Lors de la création d'une instance de MyClass, ci-dessus, les événements suivants se produisent:

  1. Le Interface IBaserequiert une fonction sans paramètre appelée SomeMethod()à fournir.
  2. La classe de base BaseClassfournit une implémentation de cette méthode - satisfaisant le besoin.
  3. Le trait myTraitfournit une fonction sans paramètre appelée SomeMethod()également, qui a priorité sur la BaseClassversion
  4. Le class MyClassfournit sa propre version de SomeMethod()- qui a priorité sur la trait-version.

Conclusion

  1. Un Interfacene peut pas fournir une implémentation par défaut d'un corps de méthode, tandis qu'un traitpeut.
  2. Un Interfaceest un polymorphe , hérité de construction - tandis qu'un traitnon.
  3. Plusieurs Interfaces peuvent être utilisés dans la même classe, ainsi que plusieurs traits.

4
"Un trait est similaire au concept C # d'une classe abstraite" Non, une classe abstraite est une classe abstraite; ce concept existe à la fois en PHP et en C #. Je comparerais plutôt un trait en PHP à une classe statique faite de méthodes d'extension en C #, avec la restriction basée sur le type supprimée car un trait peut être utilisé par à peu près n'importe quel type, contrairement à une méthode d'extension qui ne prolonge qu'un seul type.
BoltClock

1
Très bon commentaire - et je suis d'accord avec vous. En relisant, c'est une meilleure analogie. Je pense qu'il vaut mieux, cependant, de le considérer comme un 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!
Troy Alford

1
Je ne pense pas qu'il y ait de relation avec les méthodes d'extension c #. Des méthodes d'extension sont ajoutées au type de classe unique (en respectant bien sûr la hiérarchie des classes), leur but est d'améliorer un type avec des fonctionnalités supplémentaires, de ne pas "partager du code" sur plusieurs classes et de faire un gâchis. Ce n'est pas comparable! Si quelque chose doit être réutilisé, cela signifie généralement qu'il doit avoir son propre espace, comme une classe distincte qui serait liée aux classes qui ont besoin de fonctionnalités communes. L'implémentation peut varier selon la conception, mais c'est à peu près tout. Les traits sont juste une autre façon de faire un mauvais code.
Sofija

Une classe peut avoir plusieurs interfaces? Je ne sais pas si je me trompe, mais la classe X implémente Y, Z est valide.
Yann Chabot

26

Je pense qu'il traitsest 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:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

Vous pouvez avoir et utiliser cette méthode "error" dans n'importe quelle classe qui utilise ce trait.

class Something
{
    use Toolkit;

    public function do_something($zipcode)
    {
        if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
            return $this->error('Invalid zipcode.');

        // do something here
    }
}

Pendant que interfacesvous ê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 utilisant implements. Ce n'est pas le cas des traits.

C'est complètement différent!


Je pense que c'est un mauvais exemple d'un trait. to_integerserait plus probablement inclus dans une IntegerCastinterface car il n'y a aucun moyen fondamentalement similaire de convertir (intelligemment) des classes en un entier.
Matthew

5
Oubliez "to_integer" - ce n'est qu'une illustration. Un exemple. Un "Bonjour, monde". Un "example.com".
J. Bruni

2
Quel avantage ce trait de boîte à outils offre-t-il qu'une classe d'utilité autonome ne pourrait pas? Au lieu de use Toolkitvous, vous pourriez $this->toolkit = new Toolkit();ou est-ce que je manque un avantage du trait lui-même?
Anthony

@Anthony quelque part dans le Somethingconteneur que vous faitesif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101

20

Pour les débutants, la réponse ci-dessus peut être difficile, c'est la façon la plus simple de la comprendre:

Traits

trait SayWorld {
    public function sayHello() {
        echo 'World!';
    }
}

donc si vous voulez avoir une sayHellofonction dans d'autres classes sans recréer la fonction entière, vous pouvez utiliser des traits,

class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();

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

  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}

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!


5

Une métaphore souvent utilisée pour décrire les traits est que les traits sont des interfaces avec l'implémentation.

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' instanceofopé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:

class SlidingDoor extends Door implements IKeyed  
{  
    use KeyedTrait;  
    [...] // Generally not a lot else goes here since it's all in the trait  
}

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 dernière partie de cette réponse est bien sûr ce que @rdlowrey dit plus en détail dans les trois derniers paragraphes sous "Traits" de son message; Je pensais juste qu'un extrait de code squelette très simple aiderait à l'illustrer.
Jon Kloske

Je pense que la meilleure façon OO d'utiliser les traits est d'utiliser des interfaces où vous le pouvez. Et s'il y a un cas où plusieurs sous-classes implémentent le même type de code pour cette interface et que vous ne pouvez pas déplacer ce code vers leur superclasse (abstraite) -> implémentez-le avec des traits
player-one

4

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


3

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.


Je ne comprends toujours pas en quoi les traits sont différents des interfaces avec l'implémentation par défaut
denis631

@ denis631 Vous pouvez voir les traits comme des extraits de code et les interfaces comme des contrats de signature. Si cela peut aider, vous pouvez le voir comme un morceau informel d'une classe qui peut contenir n'importe quoi. Faites-moi savoir si cela aide.
Benj

Je vois que les traits PHP peuvent être vus comme des macros qui sont ensuite développées au moment de la compilation / simplement en aliasant cet extrait de code avec cette clé. Cependant, les traits de rouille semblent différents (ou je me trompe). Mais comme ils contiennent tous les deux un trait de mot, je suppose qu'ils sont les mêmes, signifiant le même concept. Lien vers les traits de rouille: doc.rust-lang.org/rust-by-example/trait.html
denis631

2

Si vous connaissez l'anglais et savez ce que cela traitsignifie, 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 tapant use.

Fondamentalement, vous pouvez le comparer à une seule variable. Les fonctions de fermeture peuvent useces 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.


2

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:

  1. Définissez une classe de base avec le code pub / sous-événement, puis les classes qui souhaitent proposer des événements peuvent l'étendre afin d'acquérir les capacités.
  2. Définissez une classe avec le code pub / sous-événement, puis les autres classes qui souhaitent proposer des événements peuvent l'utiliser via la composition, en définissant leurs propres méthodes pour encapsuler l'objet composé, en mandatant les appels de méthode.
  3. Définissez un trait avec le code pub / sous-événement, puis les autres classes qui souhaitent proposer des événements peuvent usele 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

interface Observable {
    function addEventListener($eventName, callable $listener);
    function removeEventListener($eventName, callable $listener);
    function removeAllEventListeners($eventName);
}

Et du code pour démontrer l'utilisation:

$auction = new Auction();

// Add a listener, so we know when we get a bid.
$auction->addEventListener('bid', function($bidderName, $bidAmount){
    echo "Got a bid of $bidAmount from $bidderName\n";
});

// Mock some bids.
foreach (['Moe', 'Curly', 'Larry'] as $name) {
    $auction->addBid($name, rand());
}

Ok, permet maintenant de montrer comment l'implémentation de la Auctionclasse différera lors de l'utilisation des traits.

Tout d'abord, voici à quoi ressemblerait le # 2 (en utilisant la composition):

class EventEmitter {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    private $eventEmitter;

    public function __construct() {
        $this->eventEmitter = new EventEmitter();
    }

    function addBid($bidderName, $bidAmount) {
        $this->eventEmitter->triggerEvent('bid', [$bidderName, $bidAmount]);
    }

    function addEventListener($eventName, callable $listener) {
        $this->eventEmitter->addEventListener($eventName, $listener);
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventEmitter->removeEventListener($eventName, $listener);
    }

    function removeAllEventListeners($eventName) {
        $this->eventEmitter->removeAllEventListeners($eventName);
    }
}

Voici à quoi ressemblerait le # 3 (traits):

trait EventEmitterTrait {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    protected function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    use EventEmitterTrait;

    function addBid($bidderName, $bidAmount) {
        $this->triggerEvent('bid', [$bidderName, $bidAmount]);
    }
}

Notez que le code à l'intérieur de la EventEmitterTraitest exactement le même que celui de la EventEmitterclasse, sauf que le trait déclare la triggerEvent()méthode protégée. Ainsi, la seule différence que vous devez considérer est l'implémentation de la Auctionclasse .

Et la différence est grande. Lorsque nous utilisons la composition, nous obtenons une excellente solution, nous permettant de réutiliser notre EventEmitterpar 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' Observableinterface, 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' EventEmitterobjet. 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 Auctionclasse implémente l' Observableinterface 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 EventEmitterclasse au cas où vous voudriez l'utiliser de manière compositionnelle, et définir le EventEmitterTraittrait aussi, en utilisant l' EventEmitterimplémentation de classe à l'intérieur du trait :)


1

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



0

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.