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 a través de la herencia, donde las clases hijas sobrescriben los métodos del padre. Las interfaces proporcionan otra forma poderosa de lograr el polimorfismo, especialmente cuando se trabaja con clases no relacionadas que comparten un comportamiento común.
Con las interfaces, el polimorfismo proviene del propio contrato. Cualquier clase que implemente 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 tienen ninguna relación entre sí, excepto por el hecho de implementar la misma interfaz. Esta es la ventaja clave sobre el polimorfismo basado en la herencia: puedes agrupar clases no relacionadas por sus capacidades en lugar de por su ascendencia.
Punto clave: El polimorfismo basado en interfaces permite que clases no relacionadas se utilicen de forma intercambiable basándose en un comportamiento compartido, lo que hace que su código sea más flexible y esté desacoplado de implementaciones específicas.
Desafío
FácilVamos a construir un sistema de almacenamiento que demuestre el polimorfismo basado en interfaces. Crearás diferentes 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 asegura 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 a través del constructor. Implementasave($key, $data)para que devuelva"Saving '$data' to file $this->directory/$key.txt". Implementaretrieve($key)para que devuelva"Reading from file $this->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 a través del constructor. Implementasave($key, $data)para que devuelva"Inserting '$data' into table $this->tableName with key $key". Implementaretrieve($key)para que devuelva"Selecting from table $this->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, devolviendo 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, luego el almacenamiento de base de datos. Imprime cada resultado en su propia línea. Luego usa fetchData() para recuperar del almacenamiento de archivos e imprime ese resultado.
Observa cómo storeData() y fetchData() funcionan de manera 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, sin embargo, son intercambiables a través del contrato de la interfaz.
Hoja de referencia
Las interfaces permiten el polimorfismo al permitir que clases no relacionadas se utilicen indistintamente basándose en un comportamiento compartido en lugar de en la jerarquía de herencia.
Cualquier clase que implemente una interfaz garantiza que tiene ciertos métodos, creando un contrato que permite el comportamiento polimórfico:
<?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 función notify() acepta cualquier objeto que implemente Notifiable, independientemente de la jerarquía de clases. Esto hace que el código sea más flexible y esté desacoplado de implementaciones específicas.
Ventaja clave: El polimorfismo basado en interfaces agrupa clases no relacionadas por sus capacidades, no por su ascendencia, permitiendo que implementaciones completamente diferentes se utilicen indistintamente a través de un contrato común.
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: Crear una función storeData que acepte un Storable, una clave y datos
// Debe devolver el resultado de llamar a save() con la clave y los datos
// TODO: Crear una función fetchData que acepte un Storable y una clave
// Debe devolver el resultado de llamar a retrieve() con la clave
// TODO: Crear una instancia de FileStorage con el directorio
// TODO: Crear una instancia de DatabaseStorage con el nombre de la tabla
// TODO: Usar storeData() para guardar datos con el almacenamiento de archivos, luego imprimir el resultado
// TODO: Usar storeData() para guardar datos con el almacenamiento de base de datos, luego imprimir el resultado
// TODO: Usar fetchData() para recuperar del almacenamiento de archivos, luego imprimir el resultado
?>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 OOP
Archivos externosIntroducción a OOPClases vs ObjetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Resumen - Calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasResumen - Jerarquía de empleados7Encapsulamiento
Público, Protegido, PrivadoModificadores de acceso a fondoGetters y SettersOcultamiento de informaciónPromoción de constructor (8.0)Resumen - Sistema de registros de estudiantes10Conceptos avanzados de POO
Composición vs. HerenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibers (PHP 8.1)Clonación de objetos a fondoGeneradores e Iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Espacios de nombres y Autoloading
Introducción a los espacios de nombresLa palabra clave useEstándar de Autoloading PSR-4Autoloader de ComposerResumen - Proyecto organizado5Interfaces y Contratos
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesInterfaz vs Clase AbstractaType Hinting con InterfacesResumen - 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 __unserializeResumen - 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)Resumen - Gestor de cuenta bancaria6Polimorfismo
Sobrescritura de métodos revisadaPolimorfismo mediante interfacesType Hinting y tipos de uniónEnlace estático tardíoResumen - Procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos de TraitsMétodos abstractos en TraitsTraits frente a herencia12Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy