L'héritage que nous avons vu jusqu'ici pourrait être appelé de l'héritage vertical. Cela parce qu'on le représente souvent avec un arbre généalogique de haut en bas.
Nous avons vu dans la deuxième partie de ce cours que certains langages permettent aux classes d'hériter de plusieurs classes parentes. Cela demande de gérer pas mal de problématiques, notamment liées à la surcharge des méthodes, et à l'ordre de leur résolution par le langage. C'est le problème du diamant.
Le problème du diamant (parfois appelé "diamant de la mort") est une ambiguïté qui survient lorsque deux classes B et C héritent d’une classe A, et qu’une classe D hérite à la fois de B et de C.
S'il existe une méthode dans A que B et C ont surchargée, et que D ne la surcharge pas, alors quelle version de la méthode D hérite-t-elle : celle de B, ou celle de C ?
À la différence de certains autres langages, pour éviter ce problème, il n’est pas possible d’hériter de deux classes ou plus. On peut toutefois utiliser des traits. Plutôt qu'un héritage multiple, on va offrir un moyen d'injecter du code dans une classe par le biais d'un trait.
Oui d'accord, mais c'est quoi, un trait ?
Étendez vos classes avec les traits (ou l'héritage horizontal)
Voyez les traits comme des super classes. Sauf qu'au lieu d'utiliser le mot clé class
, c'est le mot clé trait
.
Comme une classe, un trait :
possède un nom ;
peut posséder des propriétés ;
peut posséder des méthodes ;
propose de l'abstraction et de la staticité.
En plus de cela, un trait peut :
modifier la visibilité d'une méthode ou d'une propriété ;
créer des
Alias
de méthode et de propriété en cas de conflit ;être composé de plusieurs autres traits.
En revanche, un trait ne peut pas être instancié.
Enfin, une classe peut être composée de plusieurs traits. Parce qu'un trait peut potentiellement être utilisé par plusieurs arbres généalogiques d'héritage, on imagine facilement le côté horizontal de la chose.
C'est pas beau ça ? Oui et non. Comme toujours avec les fonctionnalités trop puissantes, on se retrouve vite à trop en faire. Un grand pouvoir implique de grandes responsabilités. Les traits sont très pratiques, mais peuvent vite transformer votre code en un tas de spaghettis difficile à maintenir.
Un bon cas d'usage, c'est lorsque vous souhaitez offrir des fonctionnalités qui pourraient ne pas être associées à un domaine en particulier. Si l'on reprend nos exemples des messages du chapitre précédent, on pourrait imaginer que les 2 possèdent des éléments communs.
Pour autant, ce ne serait pas logique qu'ils aient un ancêtre en commun. Ils n'appartiennent pas au même domaine, cela rendrait le couplage trop important. Les traits sont une solution. Regardons cela :
<?php
declare(strict_types=1);
namespace Domain\Mixins {
// ce trait fourni le nécessaire pour gérer du contenu
trait ContentAware {
protected string $content;
public function getContent() {
return $this->content;
}
public function setContent(string $content) {
$this->content = $content;
}
}
use Domain\user\User;
// ce trait fourni le nécessaire pour gérer un auteur
trait UserAware {
protected User $author;
public function getAuthor() {
return $this->author;
}
public function setAuthor(User $author) {
$this->author = $author;
}
}
}
namespace Domain\User {
class User {
public function __construct(public string $name){}
}
}
namespace Domain\Forum {
use Domain\Mixins;
// A présent nous avons une classe Message utilisant ces 2 traits
// En créant plus tard une seconde classe Message mais appartenant au domaine des notifications par exemple, celle ci pourra utiliser également les traits :)
class Message {
use Mixins\ContentAware, Mixins\UserAware;
}
}
namespace {
use Domain\Forum\Message;
use Domain\User\User;
$message = new Message;
$message->setContent('Hello');
$message->setAuthor(new User('greg'));
echo sprintf('%s %s', $message->getContent(), $message->getAuthor()->name);
}
Tester ce code
Amusez-vous à créer la classe Message
du domaine Notification
, puis utilisez-les !
En résumé
Les traits vous offrent la possibilité d'étendre "horizontalement" votre code.
Vous pouvez utiliser plusieurs traits dans une classe.
Un trait peut être composé d'autres traits.
Maintenant que vous savez comment surmonter le problème du diamant avec les traits, regardons un autre concept lié à l’héritage - les interfaces. Suivez-moi au prochain chapitre !