Interfaces vs. clase abstracta
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy. Lección 30 de 91.
Ahora que has aprendido tanto las interfaces como las clases abstractas, aclaremos cuándo usar cada una. Aunque puedan parecer similares, tienen propósitos diferentes y capacidades distintas.
| Característica | Interfaz | Clase abstracta |
|---|---|---|
| Métodos | Solo firmas de métodos | Métodos abstractos y concretos |
| Propiedades | Solo constantes | Puede tener propiedades |
| Herencia múltiple | Una clase puede implementar muchas | Una clase solo puede extender una |
| Constructor | Puede declarar una firma de __construct (sin cuerpo) | Puede definir un constructor con un cuerpo |
Usa una interfaz cuando quieras definir un contrato que puedan cumplir clases no relacionadas. Por ejemplo, tanto una Printer como un Logger podrían implementar una interfaz Writable, aunque no compartan ningún antecesor común.
Usa una clase abstracta cuando tengas clases relacionadas que compartan código común. Si tus clases Dog y Cat necesitan ambas la misma implementación del método eat(), una clase abstracta Animal te permite escribir ese código una sola vez.
También puedes combinar ambos enfoques. Una clase puede extender una clase abstracta mientras implementa varias interfaces:
<?php
abstract class Animal {
protected $name;
public function eat() {
return $this->name . " is eating";
}
}
interface Swimmable {
public function swim();
}
class Duck extends Animal implements Swimmable {
public function __construct($name) {
$this->name = $name;
}
public function swim() {
return $this->name . " is swimming";
}
}
Punto clave: Elige interfaces para definir capacidades entre clases no relacionadas. Elige clases abstractas cuando necesites compartir código entre clases relacionadas.
Desafío
FácilConstruyamos un sistema de vehículos que demuestre cuándo usar una clase abstracta en lugar de una interfaz, y cómo combinar ambos enfoques de manera eficaz.
Crearás un sistema en el que los vehículos compartan propiedades comunes mediante la herencia, pero también puedan adquirir capacidades adicionales mediante interfaces. Esto refleja decisiones de diseño del mundo real: un automóvil «es un» vehículo (herencia), pero también «tiene la capacidad» de ser repostable (interfaz).
Organiza tu código en cuatro archivos:
Refuelable.php: Define una interfazRefuelablecon una única signatura de método:refuel($amount). Esta capacidad puede aplicarse a cualquier clase que necesite repostar, independientemente de su jerarquía de herencia.Vehicle.php: Define una clase abstractaVehicleque proporcione funcionalidad compartida para todos los vehículos. Debe tener una propiedad protegida$brandy una propiedad protegida$fuelLevel(con un valor inicial de 0). El constructor acepta el nombre de la marca. Incluye un método concretogetBrand()que devuelva la marca, un método concretogetFuelLevel()que devuelva el nivel de combustible actual y un método abstractodrive()que las clases hijas deben implementar.Car.php: Crea una claseCarque extiendaVehiclee implementeRefuelable. Incluye ambos archivos requeridos al principio. El constructor acepta la marca y se la pasa al constructor padre. Implementadrive()para devolver"[brand] car is driving". Implementarefuel($amount)para sumar la cantidad al nivel de combustible y devolver"[brand] refueled to [fuelLevel] liters".main.php: Incluye el archivo de Car. Recibirás dos entradas: un nombre de marca y una cantidad de combustible (conviértela a un entero). Crea unCarcon la marca. Imprime el resultado dedrive()en la primera línea y, después, imprime el resultado derefuel()con la cantidad de combustible en la segunda línea.
Este desafío ilustra la diferencia clave: la clase abstracta Vehicle comparte código y establece una relación de «es un», mientras que la interfaz Refuelable añade una capacidad que podría aplicarse a otras clases no relacionadas (como un generador o un barco) sin exigirles que sean vehículos.
Pruébalo tú mismo
<?php
require_once 'Car.php';
// Leer entrada
$brand = trim(fgets(STDIN));
$fuelAmount = intval(trim(fgets(STDIN)));
// TODO: Crea un Car con la marca
// TODO: Imprime el resultado de drive() en la primera línea
// TODO: Imprime el resultado de refuel() con la cantidad de combustible en la segunda línea
?>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