En este artículo continuamos explorando el paradigma de programación orientada a objetos. En este caso vamos a hablar de la herencia entre clases. Se trata de un mecanismo que nos permite extender las propiedades o métodos de una clase en otra, sin tener que replicarlos. De este modo, podemos crear objetos (según nuestras necesidades), tanto de la clase original como de la clase derivada. También nos permite una mayor estructuración de nuestro código, optimizando la organización. Sin embargo, todas esas prestaciones no son gratuitas. El precio a pagar es que debemos dedicar mayor esfuerzo a definir la estructura interna de las clases, a fin de obtener un código limpio, mantenible y escalable. Vamos a ver qué es la herencia y cómo funciona.
DERIVANDO CLASES
Vamos a desarrollar un proyecto basado en el escenario que hemos estado empleando, en el que creamos objetos de tipo televisor. En este caso vamos a ampliar las funcionalidades de tales objetos. Para ello tenemos un directorio src/classes
en el que tenemos dos clases. La primera es CrearTelevisorClass.php
. Su código es el mismo que el del ejemplo del artículo anterior, por lo que no lo vamos a reproducir aquí.
La segunda clase está en el script TelevisorOperativoClass.php
. Su código es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
<?php /* Declaramos la clase que hace que los televisores sean operativos. Esta clase deriva de la que crea los televisores, puesto que, para que un televisor sea funcional, primero tiene que existir. */ class TelevisorOperativoClass extends CrearTelevisorClass { /* Declaramos las variables propias de esta clase como privadas. Además, esta clase, como hereda de otra, también cuenta, implícitamente, con las propiedades de la clase padre. */ private $encendido; private $volumen; private $canal; /* El constructor crea un objeto y le asigna valores a las variables del mismo, que también son privadas. Cuando una clase hereda de otra, puede invocar a un método de la clase padre usando el especificador parent:: A un argumento de un método se le puede dar un valor por defecto. */ public function __construct($color, $pulgadas, $pantalla, $encendido = false) { parent::__construct($color, $pulgadas, $pantalla); $this->encendido = $encendido; $this->volumen = 0; $this->canal = 1; } public function apagar() { $this->encendido = false; echo "El televisor está apagado.<br>"; } public function encender() { $this->encendido = true; echo "El televisor está encendido.<br>"; } public function subirVolumen() { if ($this->encendido === false) { echo "Con el televisor apagado no se puede subir el volumen.<br>"; return; } if ($this->volumen == 10) { echo "El volumen ya está al máximo. No se puede subir más.<br>"; return; } $this->volumen ++; echo "El volumen está en nivel ".$this->volumen.".<br>"; } public function bajarVolumen() { if ($this->encendido === false) { echo "Con el televisor apagado no se puede bajar el volumen.<br>"; return; } if ($this->volumen == 0) { echo "El volumen ya está al mínimo (en silencio). No se puede bajar más.<br>"; return; } $this->volumen --; echo "El volumen está en nivel ".$this->volumen.".<br>"; } public function subirCanal() { if ($this->encendido === false) { echo "Con el televisor apagado no se puede subir el canal.<br>"; return; } if ($this->canal == 35) { $this->canal = 1; } else { $this->canal ++; } echo "El canal actual es ".$this->canal.".<br>"; } public function bajarCanal() { if ($this->encendido === false) { echo "Con el televisor apagado no se puede bajar el canal.<br>"; return; } if ($this->canal == 1) { $this->canal = 35; } else { $this->canal --; } echo "El canal actual es ".$this->canal.".<br>"; } } ?> |
Veamos que tenemos aquí. En primer lugar nos vamos a fijar en la línea 5 del listado, que declara la clase:
class TelevisorOperativoClass extends CrearTelevisorClass
Fíjate que le hemos añadido la palabra reservada extends
, y el nombre de la clase de la que hereda. Esto significa que la clase TelevisorOperativoClass
va a tener, implícitamente, las propiedades y los métodos de la clase CrearTelevisorClass
, sin necesidad de declararlos en la que estamos creando ahora.
Además, fíjate en el constructor de la clase que estamos creando. Recibe, como argumentos, los mismos que recibía la clase CrearTelevisorClass
, además de otro argumento al que le establecemos un valor por defecto. Dentro del constructor llamamos, a su vez, al constructor de la clase padre, utilizando la palabra reservada parent::
. Esto es muy habitual cuando usamos clases derivadas de otras. Si necesitamos usar un método que ya está en la clase padre, y no necesitamos cambiar su funcionamiento, no reescribimos el código, sino que llamamos al método de la clase padre, pasándole los argumentos necesarios.
El resto de los métodos de la clase derivada son operativas que le añadiremos a nuestro objeto, de modo que disponga de nuevas funcionalidades.
INSTANCIANDO LA CLASE DERIVADA
Al igual que antes, la mejor manera de ver como funciona es, por supuesto, hacerla funcionar. Observa el listado (listado inicial; luego lo ampliaremos) de index.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php /* Se invocan los scripts que tienen las clases que vamos a necesitar. Se invocan los dos en este script para no "contaminar" el código de la clase TelevisorOperativoClass */ include ("src/classes/CrearTelevisorClass.php"); include ("src/classes/TelevisorOperativoClass.php"); /* Creamos un objeto llamado $miTele, usando el operador new y el nombre de la clase que queremos instanciar. Esto invoca, de forma transparente, al constructor de la clase, al que le pasa los argumentos que especificamos en la instanciación. */ $miTele = new TelevisorOperativoClass("negro", "42", "OLED"); echo "<pre>"; var_dump ($miTele); echo "</pre>"; ?> |
Observa que empezamos incluyendo los dos archivos de clase. Aunque el objeto lo vamos a crear a partir de la segunda clase (la nueva que hemos creado para este artículo), como esta, a su vez, deriva de la primera, necesitamos tener cargadas las dos. Si no, la herencia no funcionará.
Después creamos el objeto $miTele
a partir de la clase TelevisorOperativoClass
(la clase derivada). Entonces ocurren varias cosas: se invoca, automáticamente y de forma transparente, al constructor de la clase TelevisorOperativoClass
, pasándole los argumentos $color
, $pulgadas
y $pantalla
. El constructor de esta clase espera, además, un cuarto argumento, llamado $encendido
pero, cómo este ya tiene un valor por defecto en la declaración, podemos omitirlo y se asumirá dicho valor por defecto.
Dentro del constructor se llama al constructor de la clase padre, pasándole los tres primeros argumentos, que son los que éste debe recibir. Lo hacemos con la línea siguiente:
parent::__construct($color, $pulgadas, $pantalla);
En este momento tenemos un objeto de la clase derivada que, además, tiene establecidas las propiedades que se han heredado de la clase padre. Como el constructor de la clase derivada establece, además, tres propiedades que se declaran en esta, el objeto ya tiene seis propiedades. Cuando hacemos el volcado del objeto en pantalla, vemos lo siguiente:
object(TelevisorOperativoClass)#1 (6) {
["encendido":"TelevisorOperativoClass":private]=>
bool(false)
["volumen":"TelevisorOperativoClass":private]=>
int(0)
["canal":"TelevisorOperativoClass":private]=>
int(1)
["color":"CrearTelevisorClass":private]=>
string(5) "negro"
["pulgadas":"CrearTelevisorClass":private]=>
string(2) "42"
["pantalla":"CrearTelevisorClass":private]=>
string(4) "OLED"
}
Observa que tenemos un objeto de la clase TelevisorOperativoClass
, con 6 propiedades, como nos informa la primera línea. Aunque la clase hereda de otra, el objeto es de la clase derivada, no de la clase padre, porque así lo hemos indicado en el código. Sin embargo, observa la matriz de propiedades. En los índices se especifica qué propiedades se han creado en la clase derivada, y cuáles han sido heredadas de la clase padre. El objeto, no obstante, tiene todas las propiedades, con independencia de donde se hayan declarado. Si el objeto se hubiera creado desde la clase padre, sólo tendría las propiedades de esta, no las de la clase derivada.
USANDO EL OBJETO
Vamos a ampliar el script index.php
, de modo que veremos que, no solo se han heredado las propiedades, sino también los métodos. La nueva versión es la siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<?php /* Se invocan los scripts que tienen las clases que vamos a necesitar. Se invocan los dos en este script para no "contaminar" el código de la clase TelevisorOperativoClass */ include ("src/classes/CrearTelevisorClass.php"); include ("src/classes/TelevisorOperativoClass.php"); /* Creamos un objeto llamado $miTele, usando el operador new y el nombre de la clase que queremos instanciar. Esto invoca, de forma transparente, al constructor de la clase, al que le pasa los argumentos que especificamos en la instanciación. */ $miTele = new TelevisorOperativoClass("negro", "42", "OLED"); /* Mostramos el valor de la propiedad $color del objeto, lo modificamos, y mostramos el nuevo. Esto lo podemos hacer gracias a los métodos heredados de la clase padre. Así vemos que los métodos heredados de la clase padre funcionan en el objeto de la clase derivada. */ echo "El color original del televisor es ".$miTele->getColor().".<br>"; $miTele->setColor("azul"); echo "El nuevo color del televisor es ".$miTele->getColor().".<br>"; /* Ahora usamos métodos de la clase derivada. */ $miTele->subirVolumen(); $miTele->encender(); $miTele->subirVolumen(); $miTele->bajarCanal(); ?> |
Cuando ejecutes este código, verás, en la pantalla de tu navegador, lo siguiente:
El color original del televisor es negro.
El nuevo color del televisor es azul.
Con el televisor apagado no se puede subir el volumen.
El televisor está encendido.
El volumen está en nivel 1.
El canal actual es 35.
Siguiendo los comentarios que te he añadido en el código verás que no tienes ningún problema para entender cómo está funcionando.
HERENCIA MÚLTIPLE
Ya hemos mencionado que PHP (entre otros lenguajes) no admite la herencia múltiple. Existen otros lenguajes, como Python, que sí la soportan. El concepto de herencia múltiple es que una clase pueda derivar de dos o más clases simultáneamente. Vamos a pensar en nuestro objeto de tipo Televisor, al que ahora queremos añadirle una clase que permita integrar un vídeo. A la nueva clase la vamos a llamar TelevisorConVideoClass. Si quisiéramos que esta heredara de CrearTelevisorClass y TelevisorOperativoClass, en un lenguaje que soportara la herencia múltiple, la declaración de la clase sería algo así:
class TelevisorConVideo extends CrearTelevisorClass, TelevisorOperativoClass
Lo que, a su vez, nos obligaría a redefinir las dos clases anteriores, para que no hubiera herencia entre ellas. Pero, cómo PHP no soporta la herencia múltiple, esto dará un error de sintaxis.
Sin embargo, hay una manera de obtener el mismo resultado, sin romper las reglas de PHP (sólo «retorciéndolas» un poco, en nuestro provecho). Cómo quiera que TelevisorOperativoClass
ya está heredando de CrearTelevisorClass
, si hacemos que TelevisorConVideoClass
herede de TelevisorOperativoClass
, está, realmente, heredando de las dos clases. Así estamos obteniendo lo más parecido que podemos a la herencia múltiple. La clase TelevisorConVideoClass
podría quedar como vemos a continuación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php class TelevisorConVideoClass extends TelevisorOperativoClass { private $videoConectado; public function __construct($color, $pulgadas, $pantalla) { parent::__construct($color, $pulgadas, $pantalla); $this->videoConectado = false; } public function conectarVideo() { echo "El vídeo está conectado.<br>"; } public function desconectarVideo() { echo "El vídeo está desconectado.<br>"; } } ?> |
Entonces modificamos el archivo index.php
para que el objeto $miTele
sea creado a partir de TelevisorConVideoClass
, así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<?php /* Se invocan los scripts que tienen las clases que vamos a necesitar. Se invocan las tres en este script para disponer de todas las propiedades y métodos. */ include ("src/classes/CrearTelevisorClass.php"); include ("src/classes/TelevisorOperativoClass.php"); include ("src/classes/TelevisorConVideoClass.php"); /* Creamos un objeto llamado $miTele, usando el operador new y el nombre de la clase que queremos instanciar. Esto invoca, de forma transparente, al constructor de la clase, al que le pasa los argumentos que especificamos en la instanciación. */ $miTele = new TelevisorConVideoClass("negro", "42", "OLED"); echo "<pre>"; var_dump($miTele); echo "</pre>"; /* Mostramos el valor de la propiedad $color del objeto, lo modificamos, y mostramos el nuevo. Esto lo podemos hacer gracias a los métodos heredados de la clase padre. Así vemos que los métodos heredados de la clase padre funcionan en el objeto de la clase derivada. */ echo "El color original del televisor es ".$miTele->getColor().".<br>"; $miTele->setColor("azul"); echo "El nuevo color del televisor es ".$miTele->getColor().".<br>"; /* Ahora usamos métodos de la clase derivada. */ $miTele->subirVolumen(); $miTele->encender(); $miTele->subirVolumen(); $miTele->bajarCanal(); /* Por último, empleamos un método de la clase que estamos realmente usando para instanciar el objeto. */ $miTele->conectarVideo(); ?> |
En la pantalla del navegador obtenemos, ahora, lo siguiente:
object(TelevisorConVideoClass)#1 (7) {
["videoConectado":"TelevisorConVideoClass":private]=>
bool(false)
["encendido":"TelevisorOperativoClass":private]=>
bool(false)
["volumen":"TelevisorOperativoClass":private]=>
int(0)
["canal":"TelevisorOperativoClass":private]=>
int(1)
["color":"CrearTelevisorClass":private]=>
string(5) "negro"
["pulgadas":"CrearTelevisorClass":private]=>
string(2) "42"
["pantalla":"CrearTelevisorClass":private]=>
string(4) "OLED"
}
El color original del televisor es negro.
El nuevo color del televisor es azul.
Con el televisor apagado no se puede subir el volumen.
El televisor está encendido.
El volumen está en nivel 1.
El canal actual es 35.
El vídeo está conectado.
Cómo puedes ver, si lees los comentarios del código, se están usando métodos de las tres clases, de forma que tenemos el mismo resultado que si hubiéramos contado con la herencia múltiple.
CONCLUYENDO
En este artículo hemos aprendido lo necesario de la herencia de clases, y cómo funcionan los objetos de clases derivadas. En el próximo artículo hablaremos de propiedades y métodos estáticos, y de sobrescritura y sobrecarga de métodos. Los ejemplos finales de este artículo los tienes en este enlace.