Annonce de service public :
Je tiens à préciser que je pense que les traits sont presque toujours une odeur de code et devraient être évités en faveur de la composition. A mon avis, l'héritage simple est souvent 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 favorisant la composition plutôt que l'héritage (qu'il soit unique ou multiple). Si vous êtes toujours intéressé par les traits et leur relation avec les interfaces, continuez à lire ...
Commençons par dire ceci :
La programmation orientée objet (POO) peut être un paradigme difficile à appréhender. 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 en réalité les capacités de vos objets. Vous devez penser aux classes en termes de ce qu'elles peut faire au lieu de ce qu'ils en fait . C'est un contraste frappant avec la programmation procédurale traditionnelle, où l'accent est mis sur le fait de faire "faire quelque chose" à un bout de code.
Si le code OOP concerne la planification et la conception, une interface est le plan et un objet est la maison entièrement construite. Quant aux traits, ils sont simplement un moyen d'aider à construire la maison dessinée par le plan (l'interface).
Interfaces
Alors, pourquoi devrions-nous utiliser des interfaces ? Tout simplement parce que les interfaces rendent notre code moins fragile. Si vous doutez de cette affirmation, demandez à quiconque a été contraint de maintenir un code hérité qui n'était pas écrit avec des interfaces.
L'interface est un contrat entre le programmeur et son code. L'interface dit : "Tant que tu respectes mes règles, tu peux m'implémenter comme tu veux et je te promets que je ne casserai pas ton autre code".
À titre d'exemple, considérons un scénario du monde réel (sans voitures ni gadgets) :
Vous souhaitez mettre en place un système de mise en cache pour une application web afin de réduire réduire la charge du serveur
Vous commencez par écrire une classe pour mettre en cache les réponses aux requêtes en utilisant 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 vérifiez s'il y a une correspondance avec le 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 que quelques semaines plus tard, vous décidez d'utiliser un système de cache basé sur des fichiers au lieu d'APC. Vous devez alors modifier le code de votre contrôleur, car vous l'avez programmé pour qu'il fonctionne avec la fonctionnalité de l'interface de l'APC. ApcCacher
plutôt qu'à une interface qui exprime les capacités de la classe ApcCacher
classe. Imaginons qu'au lieu de ce qui précède, vous ayez créé la classe Controller
qui dépend d'une classe CacherInterface
à la place du béton ApcCacher
comme ça :
// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)
Pour aller de pair avec cela, vous définissez votre interface comme suit :
interface CacherInterface
{
public function fetch($key);
public function store($key, $data);
public function delete($key);
}
A votre tour, vous avez vos deux ApcCacher
et votre nouvelle FileCacher
mettent en œuvre l'approche CacherInterface
et vous programmez votre Controller
pour utiliser les capacités requises par l'interface.
Cet exemple démontre (nous l'espérons) comment la programmation en fonction d'une interface vous permet de modifier l'implémentation interne de vos classes sans vous inquiéter de savoir si les changements vont casser votre autre code.
Traits
Les traits, quant à eux, sont simplement une méthode de réutilisation du code. Les interfaces ne doivent pas être considérées comme une alternative mutuellement exclusive aux traits. En effet, La création de traits qui remplissent les capacités requises par une interface est le cas d'utilisation idéal. .
Vous ne devriez utiliser les traits que lorsque plusieurs classes partagent la même fonctionnalité (probablement dictée par la même interface). Il n'y a aucun sens à utiliser un trait pour fournir une fonctionnalité à une seule classe : cela ne fait qu'obscurcir ce que fait la classe et une meilleure conception consisterait à déplacer la fonctionnalité du trait dans la classe concernée.
Considérons l'implémentation du trait suivant :
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 vos deux FileCacher
et votre ApcCacher
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 (évidemment, ce n'est pas le cas dans la vie réelle, mais faites avec). Vous pourriez écrire un trait et permettre aux deux classes de l'utiliser pour répondre aux exigences de l'interface commune.
Un dernier mot d'avertissement : veillez à ne pas exagérer avec les traits. Les traits sont souvent utilisés comme une béquille pour une mauvaise conception alors que des implémentations de classes uniques suffiraient. Pour une meilleure conception du code, vous devez limiter les traits à la satisfaction des exigences de l'interface.
6 votes
N'ont pas de code dans les corps de fonction. En fait, elles n'ont pas de corps de fonction.
3 votes
Malgré ma réponse qui a fait l'objet de nombreux votes, j'aimerais que l'on précise que je suis généralement anti-trait/mixte . Consultez la transcription de ce chat pour lire comment les traits sapent souvent les bonnes pratiques de la POO .
2 votes
Je dirais le contraire. Ayant travaillé avec PHP pendant des années avant 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 comme des
Imagick
des objets, sans les lourdeurs nécessaires à l'époque des traits.1 votes
Les Traits et les interfaces sont similaires. La principale différence est que les Traits vous permettent d'implémenter les méthodes, ce qui n'est pas le cas des interfaces.