Polymorphisme via les interfaces
Fait partie de la section Programmation Orientée Objet du Journey PHP de Coddy — leçon 34 sur 91.
Dans la leçon précédente, vous avez vu le polymorphisme par héritage, où les classes enfants redéfinissent les méthodes parentes. Les interfaces offrent un autre moyen puissant de réaliser le polymorphisme, particulièrement lors de l'utilisation de classes non liées qui partagent un comportement commun.
Avec les interfaces, le polymorphisme provient du contrat lui-même. Toute classe implémentant une interface garantit qu'elle possède certaines méthodes, indépendamment de sa hiérarchie d'héritage :
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
class PushNotifier implements Notifiable {
public function send(string $message) {
return "Push: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello") . "\n";
echo notify(new SMSNotifier(), "Hello") . "\n";
echo notify(new PushNotifier(), "Hello");
Sortie :
Email: Hello
SMS: Hello
Push: HelloLa fonction notify() fonctionne avec n'importe quel objet Notifiable. Ces classes ne partagent pas de classe parente - elles sont totalement indépendantes, sauf pour l'implémentation de la même interface. C'est l'avantage clé par rapport au polymorphisme basé sur l'héritage : vous pouvez regrouper des classes sans lien par leurs capacités plutôt que par leur ascendance.
Point clé : Le polymorphisme basé sur les interfaces permet d'utiliser des classes non liées de manière interchangeable en fonction d'un comportement partagé, rendant votre code plus flexible et découplé des implémentations spécifiques.
Défi
FacileConstruisons un système de stockage qui démontre le polymorphisme basé sur les interfaces. Vous allez créer différents backends de stockage — un système de fichiers et une base de données — qui n'ont rien en commun si ce n'est qu'ils implémentent tous deux la même interface de stockage. Une seule fonction fonctionnera avec n'importe quel type de stockage, illustrant comment les interfaces permettent le polymorphisme entre des classes complètement indépendantes.
Vous organiserez votre code sur quatre fichiers :
Storable.php— Définissez une interfaceStorableavec deux signatures de méthode :save($key, $data)etretrieve($key). Ce contrat garantit que tout système de stockage peut stocker et récupérer des données, quelle que soit la manière dont il est implémenté en interne.FileStorage.php— Créez une classeFileStoragequi implémenteStorable. Incluez le fichier de l'interface. La classe doit avoir une propriété privée$directorydéfinie via le constructeur. Implémentezsave($key, $data)pour retourner"Saving '$data' to file $this->directory/$key.txt". Implémentezretrieve($key)pour retourner"Reading from file $this->directory/$key.txt".DatabaseStorage.php— Créez une classeDatabaseStoragequi implémente égalementStorable. Incluez le fichier de l'interface. La classe doit avoir une propriété privée$tableNamedéfinie via le constructeur. Implémentezsave($key, $data)pour retourner"Inserting '$data' into table $this->tableName with key $key". Implémentezretrieve($key)pour retourner"Selecting from table $this->tableName where key = $key".main.php— Incluez les deux fichiers de stockage. Créez une fonction appeléestoreDataqui accepte un paramètreStorable, une clé et des données. La fonction doit retourner le résultat de l'appel àsave()avec la clé et les données. Créez une autre fonction appeléefetchDataqui accepte un paramètreStorableet une clé, retournant le résultat de l'appel àretrieve().
Vous recevrez quatre entrées : un chemin de répertoire, un nom de table, une clé et des données à stocker. Créez un FileStorage avec le répertoire et un DatabaseStorage avec le nom de la table. Utilisez votre fonction storeData() pour sauvegarder les données en utilisant d'abord le stockage par fichier, puis le stockage par base de données. Affichez chaque résultat sur sa propre ligne. Utilisez ensuite fetchData() pour récupérer les données du stockage par fichier et affichez ce résultat.
Remarquez comment storeData() et fetchData() fonctionnent de manière identique avec les deux types de stockage — ils se soucient seulement que l'objet implémente Storable. Le système de fichiers et la base de données ont des implémentations internes complètement différentes, pourtant ils sont interchangeables grâce au contrat de l'interface.
Aide-mémoire
Les interfaces permettent le polymorphisme en permettant à des classes non liées d'être utilisées de manière interchangeable en fonction d'un comportement partagé plutôt que d'une hiérarchie d'héritage.
Toute classe implémentant une interface garantit qu'elle possède certaines méthodes, créant ainsi un contrat qui permet un comportement polymorphe :
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello");
echo notify(new SMSNotifier(), "Hello");La fonction notify() accepte n'importe quel objet implémentant Notifiable, quelle que soit la hiérarchie des classes. Cela rend le code plus flexible et découplé des implémentations spécifiques.
Avantage clé : Le polymorphisme basé sur les interfaces regroupe des classes non liées par leurs capacités, et non par leur ascendance, permettant à des implémentations complètement différentes d'être utilisées de manière interchangeable via un contrat commun.
Essayez vous-même
<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';
// Lire les entrées
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));
// TODO: Créer une fonction storeData qui accepte un Storable, une clé et des données
// Elle doit retourner le résultat de l'appel à save() avec la clé et les données
// TODO: Créer une fonction fetchData qui accepte un Storable et une clé
// Elle doit retourner le résultat de l'appel à retrieve() avec la clé
// TODO: Créer une instance de FileStorage avec le répertoire
// TODO: Créer une instance de DatabaseStorage avec le nom de la table
// TODO: Utiliser storeData() pour sauvegarder les données avec le stockage par fichier, puis afficher le résultat
// TODO: Utiliser storeData() pour sauvegarder les données avec le stockage par base de données, puis afficher le résultat
// TODO: Utiliser fetchData() pour récupérer depuis le stockage par fichier, puis afficher le résultat
?>Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Les bases de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe mot-clé $thisMéthodesPropriétésConstructeur (__construct)Destructeur (__destruct)Récapitulatif - Calculatrice simple4Héritage
Héritage de baseLe mot-clé parent::Redéfinition de méthodeLe mot-clé finalClasses abstraitesRécapitulatif - Hiérarchie des employés7Encapsulation
Public, Protégé, PrivéModificateurs d'accès en détailGetters et SettersMasquage d'informationPromotion de constructeur (8.0)Récapitulatif - Système de gestion d'étudiants10Concepts avancés de la POO
Composition vs HéritageInjection de dépendancesClasses anonymesÉnumérations (PHP 8.1)Fibers (PHP 8.1)Clonage d'objets : analyse approfondieGénérateurs et Itérateurs13Patrons de conception Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode ModèlePatron ÉtatPatron CompositePatron Repository2Espaces de noms et Autoloading
Introduction aux espaces de nomsLe mot-clé useStandard d'Autoloading PSR-4L'Autoloader ComposerRécapitulatif - Projet organisé5Interfaces et Contrats
Introduction aux InterfacesImplémentation d'InterfacesImplémentation d'Interfaces MultiplesInterface vs Classe AbstraiteType Hinting avec les InterfacesRécapitulatif - Calculateur de Formes8Méthodes magiques
Introduction aux méthodes magiques__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Clonage d'objets__serialize & __unserializeRécapitulatif - Collection personnalisée3Propriétés de classe
Propriétés d'instance vs Propriétés statiquesConstantes dans les classesMéthodes et propriétés statiquesPropriétés privées et protégéesPropriétés readonly (PHP 8.1)Récapitulatif - Gestionnaire de compte bancaire6Polymorphisme
Retour sur la redéfinition de méthodePolymorphisme via les interfacesType Hinting et Types d'unionLiaison statique tardiveRécapitulatif - Processeur de paiement9Traits
Introduction aux TraitsUtilisation de plusieurs TraitsRésolution des conflits de TraitsMéthodes abstraites dans les TraitsTraits vs Héritage12Patrons de conception Partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FabriquePatron ObservateurPatron Stratégie