PHP
Tutorial PHP: Polimorfismo
PHP: Aprende sobrescritura y sobrecarga de métodos. Conoce técnicas para emular sobrecarga y maneja el polimorfismo eficientemente. ¡Explora con ejemplos!
Aprende PHP GRATIS y certifícateSobrescritura y sobrecarga de métodos
El polimorfismo es un concepto clave en la programación orientada a objetos que permite que objetos de diferentes clases respondan de forma distinta a la misma llamada de método. Esto es posible gracias a la sobrescritura de métodos, donde una clase hija redefine un método heredado de su clase padre para modificar o extender su comportamiento.
En PHP, para sobrescribir un método, se declara en la clase hija un método con el mismo nombre y firma que en la clase padre. De esta manera, cuando se invoque el método en una instancia de la clase hija, se ejecutará la implementación de la clase hija en lugar de la original. Por ejemplo:
<?php
class Animal
{
public function emitirSonido()
{
echo "El animal hace un sonido\n";
}
}
class Perro extends Animal
{
public function emitirSonido()
{
echo "El perro ladra\n";
}
}
class Gato extends Animal
{
public function emitirSonido()
{
echo "El gato maúlla\n";
}
}
function hacerSonido(Animal $animal)
{
$animal->emitirSonido();
}
$animales = [new Perro(), new Gato()];
foreach ($animales as $animal) {
hacerSonido($animal);
}
En este ejemplo, la función hacerSonido
acepta un objeto de tipo Animal, pero gracias a la sobrescritura del método emitirSonido
, cada objeto responde acorde a su implementación específica. Esto demuestra cómo el polimorfismo permite manejar diferentes tipos de objetos a través de una interfaz común.
Por otro lado, la sobrecarga de métodos se refiere a la capacidad de definir múltiples métodos con el mismo nombre pero con diferentes parámetros. En lenguajes como Java o C++, esto es posible y se utiliza para enriquecer la funcionalidad de una clase. Sin embargo, PHP no soporta la sobrecarga de métodos de forma nativa basándose en el número o tipo de argumentos.
A pesar de esta limitación, PHP ofrece alternativas para emular la sobrecarga. Una de ellas es el uso de métodos mágicos como __call()
, que se invoca cuando se intenta acceder a un método inaccesible o inexistente en un objeto. Esto permite manejar dinámicamente las llamadas a métodos. Por ejemplo:
<?php
class OperacionesMatematicas
{
public function __call($nombre, $argumentos)
{
if ($nombre === 'calcular') {
$cantidadArgumentos = count($argumentos);
if ($cantidadArgumentos === 2) {
echo "Suma de dos números: " . ($argumentos[0] + $argumentos[1]) . "\n";
} elseif ($cantidadArgumentos === 3) {
echo "Suma de tres números: " . ($argumentos[0] + $argumentos[1] + $argumentos[2]) . "\n";
} else {
echo "Número de argumentos no soportado\n";
}
} else {
echo "Método $nombre no existe\n";
}
}
}
$operacion = new OperacionesMatematicas();
$operacion->calcular(5, 10); // Suma de dos números: 15
$operacion->calcular(1, 2, 3); // Suma de tres números: 6
$operacion->calcular(1); // Número de argumentos no soportado
En este caso, el uso de __call()
permite simular la sobrecarga al controlar la ejecución del método en función del número de argumentos recibidos. No obstante, es fundamental usar esta técnica con precaución para mantener la claridad y mantenibilidad del código.
Otra alternativa es utilizar funciones variádicas, introducidas en PHP 5.6 con el operador ...
, que permiten recibir un número variable de parámetros en un método:
<?php
class Concatenador
{
public function concatenar(...$cadenas)
{
$resultado = implode(' ', $cadenas);
echo "Resultado de la concatenación: $resultado\n";
}
}
$concatenador = new Concatenador();
$concatenador->concatenar('Hola', 'mundo'); // Resultado de la concatenación: Hola mundo
$concatenador->concatenar('Esto', 'es', 'PHP', 'variádico'); // Resultado de la concatenación: Esto es PHP variádico
Las funciones variádicas ofrecen una forma elegante de manejar múltiples parámetros sin recurrir a métodos mágicos. Además, desde PHP 8.0, es posible utilizar tipos de unión para aceptar diferentes tipos de datos en un mismo parámetro:
<?php
class Procesador
{
public function procesar(int|float $numero)
{
echo "Procesando número: $numero\n";
}
}
$procesador = new Procesador();
$procesador->procesar(10); // Procesando número: 10
$procesador->procesar(3.14); // Procesando número: 3.14
Con los tipos de unión, el método procesar
puede aceptar tanto enteros como flotantes, ampliando su flexibilidad y acercándose al concepto de sobrecarga.
Clases abstractas
Las clases abstractas en PHP son clases que no pueden ser instanciadas directamente y están diseñadas para ser extendidas por otras clases. Sirven como base para establecer una estructura común y definir métodos que las subclases deben implementar obligatoriamente. Esto permite crear jerarquías de clases coherentes y promueve el uso del polimorfismo en nuestros programas.
Para declarar una clase abstracta, se utiliza la palabra clave abstract
antes de la definición de la clase:
<?php
abstract class Vehiculo
{
protected $marca;
protected $modelo;
public function __construct($marca, $modelo)
{
$this->marca = $marca;
$this->modelo = $modelo;
}
abstract public function arrancar();
public function describir()
{
echo "Vehículo: $this->marca $this->modelo\n";
}
}
En este ejemplo, Vehiculo
es una clase abstracta que define un método abstracto arrancar()
y un método concreto describir()
. El método arrancar()
no tiene implementación y las clases que hereden de Vehiculo
estarán obligadas a definirlo.
Las subclases pueden extender la clase abstracta y deben implementar todos los métodos abstractos definidos:
<?php
class Coche extends Vehiculo
{
public function arrancar()
{
echo "El coche $this->marca $this->modelo está arrancando\n";
}
}
class Motocicleta extends Vehiculo
{
public function arrancar()
{
echo "La motocicleta $this->marca $this->modelo está arrancando\n";
}
}
$coche = new Coche('Toyota', 'Corolla');
$motocicleta = new Motocicleta('Yamaha', 'MT-07');
$coche->describir(); // Vehículo: Toyota Corolla
$coche->arrancar(); // El coche Toyota Corolla está arrancando
$motocicleta->describir(); // Vehículo: Yamaha MT-07
$motocicleta->arrancar(); // La motocicleta Yamaha MT-07 está arrancando
En este caso, tanto Coche
como Motocicleta
implementan el método arrancar()
, cumpliendo con el contrato establecido por la clase abstracta Vehiculo
. Esto asegura que todas las subclases tienen un comportamiento común y que ciertas funcionalidades están definidas de manera consistente.
Es importante destacar que no se puede crear una instancia directa de una clase abstracta. Intentar instanciarla generará un error fatal:
<?php
$vehiculo = new Vehiculo('Marca', 'Modelo'); // Error: No se puede instanciar una clase abstracta
Las clases abstractas pueden contener tanto métodos abstractos como métodos concretos. Los métodos abstractos se declaran sin cuerpo y deben ser implementados por las subclases. Los métodos concretos proporcionan una implementación que puede ser heredada o sobrescrita por las subclases si es necesario.
Además de métodos, las clases abstractas también pueden tener propiedades definidas que serán heredadas por las subclases. Esto permite compartir código y datos comunes entre varias clases relacionadas, reduciendo la duplicación y mejorando la mantenibilidad del código.
Otro uso común de las clases abstractas es definir comportamientos predeterminados que pueden ser reutilizados o modificados por las subclases. Por ejemplo:
<?php
abstract class Figura
{
protected $color;
public function __construct($color = 'negro')
{
$this->color = $color;
}
public function dibujar()
{
echo "Dibujando una figura de color $this->color\n";
}
abstract public function calcularPerimetro();
}
class Cuadrado extends Figura
{
private $lado;
public function __construct($lado, $color = 'negro')
{
parent::__construct($color);
$this->lado = $lado;
}
public function calcularPerimetro()
{
return $this->lado * 4;
}
public function dibujar()
{
echo "Dibujando un cuadrado de color $this->color\n";
}
}
$cuadrado = new Cuadrado(5, 'azul');
$cuadrado->dibujar(); // Dibujando un cuadrado de color azul
echo "Perímetro: " . $cuadrado->calcularPerimetro() . "\n"; // Perímetro: 20
En este ejemplo, Figura
es una clase abstracta que proporciona una implementación predeterminada del método dibujar()
. La clase Cuadrado
hereda este método, pero decide sobrescribirlo para proporcionar una implementación más específica. Sin embargo, está obligada a implementar el método abstracto calcularPerimetro()
.
Las clases abstractas son especialmente útiles cuando se desea crear una jerarquía de clases con un comportamiento compartido y cuando se necesita asegurar que ciertas características sean implementadas por todas las subclases. Esto resulta en un código más estructurado y en una mejor organización de las responsabilidades dentro de la aplicación.
Es importante también entender la diferencia entre una clase abstracta y una interfaz. Mientras que las clases abstractas pueden contener métodos tanto abstractos como concretos, una interfaz (hasta PHP 8) solo declara métodos sin implementación. A partir de PHP 8, las interfaces pueden tener métodos con implementación mediante default methods
, pero las clases abstractas siguen siendo la elección adecuada cuando se necesita combinar implementación y definición en una clase base.
Otro aspecto a considerar es que las clases abstractas permiten definir propiedades y métodos con visibilidad protected
o private
, lo que facilita el encapsulamiento y la protección de datos internos. Las subclases pueden acceder a estas propiedades y métodos según corresponda, mientras que permanecen ocultos para código externo.
Métodos abstractos
Los métodos abstractos son declaraciones de métodos sin implementación que se utilizan en clases abstractas para definir una interfaz que las clases hijas deben cumplir. Estos métodos establecen un contrato obligatorio, asegurando que todas las subclases proporcionen su propia versión específica del método declarado.
Para declarar un método abstracto, se utiliza la palabra clave abstract
antes de la declaración del método, y este no debe contener un cuerpo. Por ejemplo:
<?php
abstract class Animal
{
protected $nombre;
public function __construct($nombre)
{
$this->nombre = $nombre;
}
abstract public function hacerSonido();
}
En este ejemplo, la clase Animal
es abstracta y contiene un método abstracto hacerSonido()
. Las clases que hereden de Animal
estarán obligadas a implementar este método, proporcionando una implementación concreta de cómo el animal hace un sonido.
Al implementar una subclase, es necesario definir todos los métodos abstractos heredados. De lo contrario, la subclase también debe declararse como abstracta. Veamos cómo se implementan las subclases:
<?php
class Perro extends Animal
{
public function hacerSonido()
{
echo "El perro $this->nombre ladra\n";
}
}
class Gato extends Animal
{
public function hacerSonido()
{
echo "El gato $this->nombre maúlla\n";
}
}
$perro = new Perro('Firulais');
$gato = new Gato('Misu');
$perro->hacerSonido(); // El perro Firulais ladra
$gato->hacerSonido(); // El gato Misu maúlla
En este caso, tanto Perro
como Gato
implementan el método abstracto hacerSonido()
, proporcionando su propio comportamiento. Esto permite utilizar el polimorfismo para tratar objetos de diferentes clases de forma uniforme:
<?php
$animales = [new Perro('Bobby'), new Gato('Luna')];
foreach ($animales as $animal) {
$animal->hacerSonido();
}
Los métodos abstractos pueden incluir parámetros y tipos de retorno, lo que permite definir contratos más precisos. A partir de PHP 7, es posible especificar tipos escalar y de retorno, y desde PHP 8, se pueden utilizar tipos de unión. Por ejemplo:
<?php
abstract class Operacion
{
abstract public function calcular(float $a, float $b): float;
}
class Suma extends Operacion
{
public function calcular(float $a, float $b): float
{
return $a + $b;
}
}
class Multiplicacion extends Operacion
{
public function calcular(float $a, float $b): float
{
return $a * $b;
}
}
$suma = new Suma();
echo "Resultado de la suma: " . $suma->calcular(5, 3) . "\n"; // Resultado de la suma: 8
$multiplicacion = new Multiplicacion();
echo "Resultado de la multiplicación: " . $multiplicacion->calcular(5, 3) . "\n"; // Resultado de la multiplicación: 15
Es importante destacar que los métodos abstractos no pueden ser privados, ya que deben ser accesibles para ser implementados por las subclases. Sin embargo, sí pueden ser protegidos (protected
) o públicos (public
), controlando así su visibilidad:
<?php
abstract class Servicio
{
abstract protected function procesar();
public function iniciar()
{
$this->procesar();
}
}
class ServicioWeb extends Servicio
{
protected function procesar()
{
echo "Procesando servicio web\n";
}
}
$servicio = new ServicioWeb();
$servicio->iniciar(); // Procesando servicio web
Además, los métodos abstractos pueden ser estáticos. Para declararlos, se utiliza la palabra clave static
junto con abstract
. Las subclases deben implementar el método estático correspondiente:
<?php
abstract class Utilidad
{
abstract public static function convertir($valor);
}
class ConversorMayusculas extends Utilidad
{
public static function convertir($valor)
{
return strtoupper($valor);
}
}
echo ConversorMayusculas::convertir('hola mundo') . "\n"; // HOLA MUNDO
Los métodos abstractos también pueden declarar valores por defecto en sus parámetros, lo que permite especificar comportamiento predeterminado que las subclases pueden utilizar o sobrescribir:
<?php
abstract class Mensaje
{
abstract public function saludar($nombre = 'Invitado');
}
class SaludoFormal extends Mensaje
{
public function saludar($nombre = 'Señor/Señora')
{
echo "Buenos días, $nombre\n";
}
}
$saludo = new SaludoFormal();
$saludo->saludar(); // Buenos días, Señor/Señora
$saludo->saludar('María'); // Buenos días, María
Al utilizar métodos abstractos, se promueve el uso de la abstracción, uno de los pilares fundamentales de la programación orientada a objetos. Esto facilita la creación de componentes reutilizables y extensibles, donde las clases hijas proporcionan implementaciones específicas manteniendo una interfaz común.
En contextos donde se manejan colecciones de objetos de diferentes clases que comparten un comportamiento común, los métodos abstractos permiten escribir código más genérico y flexible. Por ejemplo, en una aplicación gráfica podríamos tener:
<?php
abstract class ControlUI
{
abstract public function renderizar();
}
class Boton extends ControlUI
{
public function renderizar()
{
echo "Renderizando un botón\n";
}
}
class CampoTexto extends ControlUI
{
public function renderizar()
{
echo "Renderizando un campo de texto\n";
}
}
$controles = [new Boton(), new CampoTexto()];
foreach ($controles as $control) {
$control->renderizar();
}
En este ejemplo, cada control de interfaz de usuario implementa el método abstracto renderizar()
, y se puede iterar sobre ellos sin conocer su tipo específico, aprovechando el polimorfismo.
Es fundamental comprender que los métodos abstractos no pueden contener ninguna implementación. Intentar proporcionar un cuerpo a un método declarado como abstracto resultará en un error de sintaxis. Esto garantiza que la responsabilidad de la implementación recae completamente en las clases hijas.
Finalmente, los métodos abstractos son esenciales para definir comportamientos comunes que deben ser personalizados por cada subclase, permitiendo diseñar sistemas más robustos y coherentes. Su uso adecuado contribuye a mejorar la arquitectura del software y facilita el mantenimiento y la escalabilidad de las aplicaciones.
Uso de palabra clave ‘final’
La palabra clave final en PHP se utiliza para restringir la herencia de clases y la sobrescritura de métodos. Al declarar una clase o un método como final, se impide que las clases hijas puedan extender o modificar su comportamiento, lo que es útil para preservar la integridad y consistencia del código.
Cuando se declara una clase final, se está especificando que esa clase no puede ser heredada por ninguna otra clase. Esto se logra anteponiendo la palabra final a la declaración de la clase:
<?php
final class Base
{
public function mensaje()
{
echo "Mensaje desde la clase Base\n";
}
}
En este ejemplo, la clase Base
es final, por lo que intentar heredar de ella generará un error:
<?php
class Derivada extends Base
{
// Implementación
}
// Error fatal: No se puede heredar de una clase final Base
Utilizar clases finales es apropiado cuando se desea evitar que la funcionalidad de una clase sea alterada a través de la herencia. Esto es especialmente relevante en el desarrollo de bibliotecas o APIs, donde es fundamental que ciertas clases permanezcan inmutables para garantizar su correcto funcionamiento.
Además de clases, también es posible declarar métodos como finales dentro de una clase. Un método final no puede ser sobrescrito por ninguna clase que herede de la clase que lo define. La sintaxis es la siguiente:
<?php
class Padre
{
final public function metodoImportante()
{
echo "Este método es final y no puede ser sobrescrito\n";
}
public function metodoComun()
{
echo "Este método puede ser sobrescrito\n";
}
}
class Hijo extends Padre
{
// Intento de sobrescribir un método final
public function metodoImportante()
{
echo "Intentando sobrescribir el método final\n";
}
// Sobrescritura permitida de un método no final
public function metodoComun()
{
echo "Método comun sobrescrito en la clase Hijo\n";
}
}
// Error fatal: Cannot override final method Padre::metodoImportante()
En este caso, la clase Hijo
intenta sobrescribir el método final metodoImportante()
, lo cual provoca un error. Sin embargo, puede sobrescribir metodoComun()
sin inconvenientes, ya que no ha sido declarado como final en la clase Padre
.
El uso de métodos finales es adecuado cuando se desea asegurar que ciertos métodos críticos mantengan su comportamiento original, evitando que puedan ser modificados en las subclases. Esto es esencial para mantener la estabilidad y previsibilidad del código en aplicaciones complejas.
Es importante destacar que una clase no final puede contener métodos finales. Esto permite controlar con precisión qué partes de una clase pueden ser extendidas o modificadas, proporcionando flexibilidad y seguridad al diseño de la aplicación.
Por otro lado, no es posible declarar un método como final y abstracto al mismo tiempo. Un método abstracto es aquel que no tiene implementación y obliga a las clases hijas a definirlo, mientras que un método final no puede ser sobrescrito. Por lo tanto, combinarlos no tiene sentido y PHP generará un error:
<?php
abstract class Proceso
{
final abstract public function ejecutar();
}
// Error fatal: Cannot use the final modifier on an abstract method
Sin embargo, es válido tener una clase abstracta con métodos finales que posean una implementación concreta. Las subclases pueden heredar estos métodos pero no podrán sobrescribirlos. Esto es útil cuando se desea proporcionar funcionalidad común que no debe ser alterada:
<?php
abstract class Conexion
{
final public function establecerConexion()
{
echo "Conexión establecida\n";
}
abstract public function ejecutarConsulta($query);
}
class ConexionMySQL extends Conexion
{
public function ejecutarConsulta($query)
{
echo "Ejecutando consulta en MySQL: $query\n";
}
}
$conexion = new ConexionMySQL();
$conexion->establecerConexion(); // Conexión establecida
$conexion->ejecutarConsulta('SELECT * FROM usuarios');
En el contexto del polimorfismo, el uso de final tiene implicaciones significativas. Al impedir la sobrescritura de métodos, se limita la capacidad de las subclases para modificar comportamientos heredados, lo que puede ser deseable para mantener ciertas funcionalidades intactas. Sin embargo, esto también restringe la flexibilidad que ofrece el polimorfismo, ya que las clases hijas no pueden adaptar estos métodos a sus necesidades específicas.
Por ejemplo, si se tiene una clase Figura
con un método final dibujar()
, las subclases no podrán proporcionar sus propias implementaciones de cómo deben dibujarse, lo que podría ser limitante:
<?php
class Figura
{
final public function dibujar()
{
echo "Dibujando una figura genérica\n";
}
}
class Circulo extends Figura
{
public function dibujar()
{
echo "Dibujando un círculo\n";
}
}
// Error fatal: Cannot override final method Figura::dibujar()
Por lo tanto, es esencial utilizar la palabra clave final de manera equilibrada. Si bien ofrece control y seguridad sobre el comportamiento de las clases y métodos, su uso indiscriminado puede obstaculizar la extensibilidad y reutilización del código. Es recomendable evaluar cuidadosamente si es necesario restringir la herencia o la sobrescritura en cada caso específico.
Un escenario común donde el uso de final es apropiado es en el patrón de diseño Template Method, donde se define un método final que establece el esqueleto de un algoritmo, delegando algunas operaciones a métodos abstractos que las subclases deben implementar:
<?php
abstract class Proceso
{
final public function ejecutar()
{
$this->iniciar();
$this->procesar();
$this->finalizar();
}
protected function iniciar()
{
echo "Iniciando proceso\n";
}
abstract protected function procesar();
protected function finalizar()
{
echo "Finalizando proceso\n";
}
}
class ProcesoConcreto extends Proceso
{
protected function procesar()
{
echo "Procesando datos en ProcesoConcreto\n";
}
}
$proceso = new ProcesoConcreto();
$proceso->ejecutar();
En este ejemplo, el método ejecutar()
es final y define el flujo del proceso. Las subclases pueden personalizar partes del algoritmo implementando el método procesar()
, pero no pueden alterar el flujo general definido en ejecutar()
.
Todas las lecciones de PHP
Accede a todas las lecciones de PHP y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Php
Introducción Y Entorno
Instalación Y Primer Programa De Php
Introducción Y Entorno
Tipos De Datos, Variables Y Constantes
Sintaxis
Operadores Y Expresiones
Sintaxis
Estructuras De Control
Sintaxis
Funciones Y Llamada De Funciones
Sintaxis
Cadenas De Texto Y Manipulación
Sintaxis
Manejo De Números
Sintaxis
Manejo De Fechas Y Tiempo
Sintaxis
Manejo De Arrays
Sintaxis
Introducción A La Poo En Php
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Constructores Y Destructores
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Traits
Programación Orientada A Objetos
Namespaces
Programación Orientada A Objetos
Autoloading De Clases
Programación Orientada A Objetos
Manejo De Errores Y Excepciones
Programación Orientada A Objetos
Manejo De Archivos
Programación Orientada A Objetos
Patrones De Diseño
Programación Orientada A Objetos
Introducción A Los Formularios En Php
Formularios
Procesamiento De Datos De Formularios
Formularios
Manejo De Archivos En Formularios
Formularios
Redirecciones Y Retroalimentación Al Usuario
Formularios
Formularios Dinámicos Y Separación De Lógica
Formularios
Introducción A La Persistencia En Php
Persistencia
Conexión A Bases De Datos
Persistencia
Consultas Y Operaciones Crud
Persistencia
Gestión De Transacciones
Persistencia
Manejo De Errores Y Excepciones En Base De Datos
Persistencia
Patrones De Acceso A Datos
Persistencia
Concepto De Sesiones En Php
Sesiones Y Cookies
Configuración De Sesiones
Sesiones Y Cookies
Cookies
Sesiones Y Cookies
Manejo Avanzado De Sesiones Y Cookies
Sesiones Y Cookies
Principales Vulnerabilidades En Php
Seguridad
Seguridad En Formularios Y Entrada De Datos
Seguridad
Protección Frente A Inyección Sql
Seguridad
Gestión De Contraseñas Y Cifrado
Seguridad
Seguridad En Sesiones Y Cookies
Seguridad
Configuraciones De Php Para Seguridad
Seguridad
Introducción Al Testing En Php
Testing
Phpunit
Testing
Cobertura De Código En Testing
Testing
Test Doubles (Mocks, Stubs, Fakes, Spies)
Testing
Pruebas De Integración Y Funcionales
Testing
En esta lección
Objetivos de aprendizaje de esta lección
- Entender el polimorfismo en programación orientada a objetos.
- Saber implementar sobrescritura de métodos en PHP.
- Conocer las limitaciones de la sobrecarga de métodos en PHP.
- Utilizar métodos mágicos para emular sobrecarga.
- Aplicar funciones variádicas para gestionar múltiples parámetros.