Classes abstraites, partie 1 – Comportement d’abstraction
Il y a environ un mois, j’ai écrit sur l’un des piliers de la programmation orientée objet (en particulier l’abstraction). Dans l’article, j’ai défini l’abstraction comme suit :
Au lieu de cela, nous allons extraire des idées dans leurs classes. Et il y a une idée clé ici: une classe doit représenter un nom.
Et bien que ce soit toujours vrai, l’idée de classes abstraites est quelque chose de différent dans la programmation orientée objet.
Cela semble déroutant, non ? C’est-à-dire:
- à un niveau, nous avons l’abstraction étant définie comme l’idée que nous prenons une idée et la représentons dans une classe,
- à un autre niveau, nous avons des classes abstraites qui sont utilisées pour aider à définir les fonctions que les sous-classes doivent implémenter.
Et si ce n’est pas assez déroutant, nous mélangeons cela avec des interfaces qui fournissent un contrat implémentant des classes qui doivent suivre, puis nous le mélangeons avec des classes abstraites qui définissent des méthodes qui doivent également être implémentées mais qui peuvent également implémenter leurs propres méthodes.
Confus encore? Pas de soucis. Le but des trois articles suivants est de faire ce qui suit :
- Définir ce que sont les classes abstraites,
- Décrire les différentes classes abstraites et interfaces,
- Aidez à décider quand vous voulez utiliser l’un plutôt que l’autre.
Cela dit, voici toute l’idée derrière les classes abstraites.
Comportement d’abstraction
Tout d’abord, il y a une différence entre l’abstraction et les classes abstraites. Le premier fait référence à l’idée de représenter quelque chose dans la programmation ; ce dernier fait référence à une manière réelle d’écrire du code.
Et l’une des meilleures façons que j’ai trouvées de penser aux classes abstraites dans la programmation orientée objet est de les considérer comme ceci :
Les classes abstraites remplacent l’implémentation.
Peut-être une autre façon de les considérer comme des espaces réservés. En fin de compte, ils fournissent le comportement que les sous-classes doivent implémenter.
En quoi est-ce différent d’une interface ? Rappelez-vous qu’une interface définit une signature pour une fonction (le nom de la fonction, ses arguments et ses modificateurs de visibilité) qu’une classe doit implémenter.
L’abstraction, d’autre part, fournit un substitut à l’implémentation qu’une sous-classe doit implémenter. Mais peut-être cela est-il mieux démontré par l’utilisation de code.
L’abstraction en pratique
Disons que vous travaillez sur un projet et que vous constatez que vous avez des fonctionnalités qui existent à plusieurs endroits. En plus de violer toute l’idée de DRY, il a également le potentiel d’être un endroit où vous pouvez extraire une fonctionnalité dans une classe de base et l’utiliser à nouveau.
Considérons cela dans le contexte d’un système de publication. Ce n’est pas nécessairement ainsi que WordPress l’implémente, mais il utilise une idée avec laquelle nous sommes familiers: les taxonomies.
Dans WordPress, rappelons que nous avons des Tags et des Catégories. Il existe des différences subtiles entre les deux (comme si l’un était hiérarchique ou non), mais ils partagent également des attributs similaires, comme avoir un nom et une limace.
Une abstraction taxonomique
Nous pouvons donc commencer par écrire une classe de taxonomie abstraite qui résume la fonctionnalité commune dans sa propre classe.
<?php
abstract class Taxonomy
{
private $taxonomyName;
private $taxonomySlug;
public function __construct($name) {
$this->taxonomyName = $name;
$this->taxonomySlug = strtolower(str_ireplace(' ', '-', $this->taxonomyName));
}
public function getName() {
return $this->taxonomyName;
}
public function getSlug() {
return $this->taxonomySlug;
}
abstract protected function isHierarchical();
abstract protected function isCategory();
abstract protected function isTag();
}
Dans le code ci-dessus, vous verrez que j’ai fait ce qui suit :
- a déclaré la classe abstraite
- défini plusieurs attributs qui seront définis dans le constructeur
- a assuré la mise en œuvre de plusieurs fonctions publiques,
- ajouté plusieurs méthodes protégées.
La clé à retenir de l’examen de cette classe est que toute classe qui implémente cette classe abstraite aura automatiquement la fonctionnalité définie dans le constructeur, la fonction getName et la fonction getSlug.
Ils n’auront cependant pas l’implémentation des fonctions abstraites. C’est ce qui reste à implémenter par les sous-classes (que je partagerai momentanément).
Une taxonomie concrète : une étiquette
Maintenant que nous avons défini une classe abstraite, il est possible d’implémenter l’abstraction. Par exemple :
<?php
class Tag extends Taxonomy
{
protected function isHierarchical() {
return false;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
}
Dans le code ci-dessus, notez que la classe ne fait que fournir l’implémentation des fonctions abstraites définies dans la classe abstraite (qui est spécifiée par la fonction extend dans la définition de classe).
Plus loin dans cet article, je partagerai comment tester ce code, mais notez que ce qui précède offre non seulement les fonctionnalités que vous voyez, mais également les fonctionnalités de la classe Taxonomy.
Une taxonomie concrète: une catégorie
Avant de jeter un coup d’œil à cela en action, je souhaite également définir une catégorie. Cela inclura du code qui implémente des fonctions de la classe abstraite mais aussi des fonctions qui lui sont propres.
<?php
class Category extends Taxonomy
{
private $parentId = -1;
protected function isHierarchical() {
return true;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
public function setParentId($parentId) {
$this->parentId = $parentId;
}
public function getParentId() {
return $this->parentId;
}
}
Ici, nous avons tout ce qui vient avec la classe Taxonomy, mais nous avons également défini notre propriété pour son ID parent et ses méthodes getter et setter. Bien que triviale dans ce cas, elle montre comment les catégories, qui sont hiérarchiques, peuvent fonctionner.
De plus, si la catégorie n’a pas de parent, l’ID est défini sur -1, ce qui facilite l’écriture pour les tests automatisés ou même la vérification pour voir si elle a un parent.
Le voir en action
Pour faire la démonstration de tout ce code, j’ai un essentiel qui inclut tout le code dans un seul fichier. En tant que meilleure pratique, je ne le recommande pas. Au lieu de cela, chaque classe doit être conservée dans son propre fichier et chaque classe doit appartenir à un espace de noms.
Mais comme c’est purement à des fins de démonstration, cela suffit.
<?php
abstract class Taxonomy
{
private $taxonomyName;
private $taxonomySlug;
public function __construct($name) {
$this->taxonomyName = $name;
$this->taxonomySlug = strtolower(str_ireplace(' ', '-', $this->taxonomyName));
}
public function getName() {
return $this->taxonomyName;
}
public function getSlug() {
return $this->taxonomySlug;
}
abstract protected function isHierarchical();
abstract protected function isCategory();
abstract protected function isTag();
}
/*--*/
class Tag extends Taxonomy
{
protected function isHierarchical() {
return false;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
}
/*--*/
class Category extends Taxonomy
{
private $parentId = -1;
protected function isHierarchical() {
return true;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
public function setParentId($parentId) {
$this->parentId = $parentId;
}
public function getParentId() {
return $this->parentId;
}
}
/*- Tag Demo ----------------------------*/
$tag = new Tag('Acme Tag');
echo $tag->getName();
echo $tag->getSlug();
/*- Category Demo -----------------------*/
$category = new Category('Acme Category');
echo $category->getName();
echo $category->getSlug();
echo $category->getParentId();
$category->setParentId(100);
echo $category->getparentId();
Lorsque vous exécutez ceci dans la console, vous devriez voir quelque chose comme la sortie suivante :
Vous devrez peut-être ajouter quelques instructions d’ écho pour vous assurer qu’il crée de nouvelles lignes, mais cela dépend de vous.
Qu’en est-il des interfaces ?
Donc, à ce stade :
- nous avons une définition de travail de ce que sont les classes abstraites,
- nous avons un exemple de ce à quoi ressemblent les classes abstraites,
- et nous avons une démonstration de travail de la façon dont ils peuvent fonctionner.
Ensuite, je vais approfondir la discussion sur les différences entre les classes abstraites et les interfaces, quand vous voudrez peut-être les utiliser les unes sur les autres, ou quand vous voudrez peut-être les utiliser conjointement les unes avec les autres.