Polimorfismo mediante interfaces
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy. Lección 34 de 91.
En la lección anterior, viste el polimorfismo mediante la herencia, donde las clases secundarias sobrescriben los métodos de las clases principales. Las interfaces proporcionan otra forma eficaz de lograr el polimorfismo, especialmente al trabajar con clases no relacionadas que comparten un comportamiento común.
Con las interfaces, el polimorfismo proviene del propio contrato. Cualquier clase que implementa una interfaz garantiza que tiene ciertos métodos, independientemente de su jerarquía de herencia:
<?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");
Salida:
Email: Hello
SMS: Hello
Push: HelloLa función notify() funciona con cualquier objeto Notifiable. Estas clases no comparten una clase padre; no están relacionadas en absoluto, excepto por implementar la misma interfaz. Esta es la ventaja clave frente al polimorfismo basado en la herencia: puedes agrupar clases no relacionadas según sus capacidades en lugar de su ascendencia.
Punto clave: El polimorfismo basado en interfaces permite usar clases no relacionadas indistintamente en función de un comportamiento compartido, lo que hace que tu código sea más flexible y esté desacoplado de implementaciones específicas.
Desafío
FácilConstruyamos un sistema de almacenamiento que demuestre el polimorfismo basado en interfaces. Crearás distintos backends de almacenamiento, un sistema de archivos y una base de datos, que no tienen nada en común, excepto que ambos implementan la misma interfaz de almacenamiento. Una sola función funcionará con cualquier tipo de almacenamiento, mostrando cómo las interfaces permiten el polimorfismo entre clases completamente no relacionadas.
Organizarás tu código en cuatro archivos:
Storable.php: Define una interfazStorablecon dos firmas de método:save($key, $data)yretrieve($key). Este contrato garantiza que cualquier sistema de almacenamiento pueda guardar y recuperar datos, independientemente de cómo esté implementado internamente.FileStorage.php: Crea una claseFileStorageque implementeStorable. Incluye el archivo de la interfaz. La clase debe tener una propiedad privada$directoryestablecida mediante el constructor. Implementasave($key, $data)para que devuelva"Saving '[data]' to file [directory]/[key].txt". Implementaretrieve($key)para que devuelva"Reading from file [directory]/[key].txt".DatabaseStorage.php: Crea una claseDatabaseStorageque también implementeStorable. Incluye el archivo de la interfaz. La clase debe tener una propiedad privada$tableNameestablecida mediante el constructor. Implementasave($key, $data)para que devuelva"Inserting '[data]' into table [tableName] with key [key]". Implementaretrieve($key)para que devuelva"Selecting from table [tableName] where key = [key]".main.php: Incluye ambos archivos de almacenamiento. Crea una función llamadastoreDataque acepte un parámetroStorable, una clave y datos. La función debe devolver el resultado de llamar asave()con la clave y los datos. Crea otra función llamadafetchDataque acepte un parámetroStorabley una clave, y devuelva el resultado de llamar aretrieve().
Recibirás cuatro entradas: una ruta de directorio, un nombre de tabla, una clave y algunos datos para almacenar. Crea un FileStorage con el directorio y un DatabaseStorage con el nombre de la tabla. Usa tu función storeData() para guardar los datos usando primero el almacenamiento de archivos y después el almacenamiento de base de datos. Imprime cada resultado en su propia línea. Después, usa fetchData() para recuperar datos del almacenamiento de archivos e imprime ese resultado.
Observa cómo storeData() y fetchData() funcionan de forma idéntica con ambos tipos de almacenamiento. Solo les importa que el objeto implemente Storable. El sistema de archivos y la base de datos tienen implementaciones internas completamente diferentes; aun así, son intercambiables mediante el contrato de la interfaz.
Pruébalo tú mismo
<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';
// Leer entradas
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));
// TODO: Crea una función storeData que acepte un Storable, key y data
// Debería devolver el resultado de llamar a save() con la key y data
// TODO: Crea una función fetchData que acepte un Storable y key
// Debería devolver el resultado de llamar a retrieve() con la key
// TODO: Crea una instancia de FileStorage con el directorio
// TODO: Crea una instancia de DatabaseStorage con el nombre de la tabla
// TODO: Usa storeData() para guardar data con el almacenamiento de archivos, luego imprime el resultado
// TODO: Usa storeData() para guardar data con el almacenamiento de base de datos, luego imprime el resultado
// TODO: Use fetchData() to retrieve from file storage, then print the result
?>Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de la POO
Archivos externosIntroducción a la POOClases frente a objetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Repaso: calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasRepaso - Jerarquía de empleados7Encapsulación
Público, protegido, privadoModificadores de acceso en profundidadGetters y settersOcultación de la informaciónPromoción del constructor (8.0)Repaso: sistema de registros de estudiantes10Conceptos avanzados de OOP
Composición vs. herenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibras (PHP 8.1)Análisis profundo de la clonación de objetosGeneradores e iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Namespaces y carga automática
Introducción a los NamespacesLa palabra clave useEstándar de carga automática PSR-4Autoloader de ComposerRepaso: proyecto organizado5Interfaces y contratos
Introducción a las interfacesImplementación de interfacesImplementación de múltiples interfacesInterfaces vs. clase abstractaTipado con interfacesRepaso - Calculadora de formas8Métodos mágicos
Introducción a los métodos mágicos__toString y __debugInfo__get, __set, __isset, __unset__call y __callStatic__clone y clonación de objetos__serialize y __unserializeRepaso: colección personalizada3Propiedades de clase
Propiedades de instancia vs. estáticasConstantes en clasesMétodos y propiedades estáticosPropiedades privadas y protegidasPropiedades de solo lectura (PHP 8.1)Repaso: gestor de cuentas bancarias6Polimorfismo
Repaso de la sobrescritura de métodosPolimorfismo mediante interfacesAnotaciones de tipo y tipos uniónEnlace estático tardíoRepaso: procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos entre TraitsMétodos abstractos en TraitsTraits frente a la herencia12Patrones de diseño, parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón StrategyPractica por tu cuenta: Compilador de PHP online