Programación orientada a objetos en PHP 5: Herencia

Curso Programación Orientada Objetos en PHP
Vamos a recuperar el código del artículo anterior pero con unas pequeñas diferencias. He hecho un poco de limpieza para que se vea más claro el tema que vamos a tratar en esta entrega: la herencia.

En el nuevo código tenemos las propiedades $titulo y $cuerpo junto con sus getters y setters. Hablé de los getters y los setters en el artículo de encapsulación.

class Noticia
{
    private $titulo;
    private $cuerpo;

    public function getTitulo()
    {
        return $this->titulo;
    }
    
    public function setTitulo($titulo)
    {
        $this->titulo = $titulo;
    }

    public function getCuerpo()
    {
        return $this->cuerpo;
    }

    public function setCuerpo($cuerpo)
    {
        $this->cuerpo = $cuerpo;
    }
}

Esta clase es válida para una noticia general. Ahora imagina que nos llaman para un nuevo proyecto. En este otro proyecto nos piden que añadamos noticias de fútbol y nos dicen que es muy importante que existan unas nuevas propiedades: los equipos que juegan y el resultado del partido.

Entonces podemos coger esta clase y hacer un fantástico copia/pega (lo que algunos piensan que es reutilizar código) y empezamos a hacerle cambios.

Creamos una nueva propiedad para el resultado del partido y las propiedades para almacenar los equipos que han jugado. Algo así:

    private $equipo1;
    private $equipo2;
    private $resultado;

Y sus correspondientes getters y setters:

class Noticia
{
    private $titulo;
    private $cuerpo;
    private $equipo1;
    private $equipo2;
    private $resultado;


    public function getEquipo1()
    {
        return $this->equipo1;
    }

    public function setEquipo1($equipo1)
    {
        $this->equipo1 = $equipo1;
    }

    public function getEquipo2()
    {
        return $this->equipo2;
    }

    public function setEquipo2($equipo2)
    {
        $this->equipo2 = $equipo2;
    }

    public function getResultado()
    {
        return $this->resultado;
    }

    public function setResultado($resultado)
    {
        $this->resultado = $resultado;
    }

    public function getTitulo()
    {
        return $this->titulo;
    }

    public function getTitulo()
    {
        return $this->titulo;
    }
    
    public function setTitulo($titulo)
    {
        $this->titulo = $titulo;
    }

    public function getCuerpo()
    {
        return $this->cuerpo;
    }

    public function setCuerpo($cuerpo)
    {
        $this->cuerpo = $cuerpo;
    }
}

Bueno, esto empieza a crecer un poco. Pero estamos muy orgullosos de nuestro trabajo.

Vuelve a llamar el jefe. Resulta que también tiene que haber noticias de ciclismo. Y debe haber una propiedad que contenga el nombre del ganador de la etapa y el nombre del ciclista que va en cabeza en la general.

¿Ahora qué hacemos? ¿Creamos otra clase para este otro tipo de noticias? ¿O añadimos estas nuevas propiedades a la clase que tenemos?

¡Qué dilema! Si creamos otra clase vamos a tener un montón de código repetido (toda la parte del título y del cuerpo de la noticia y los métodos relacionados). ¿No se suponía que esto de los objetos permitía reutilizar código?

Así que nos decidimos por la opción de añadir las nuevas propiedades a nuestra clase:

    private $ganadorEtapa;
    private $ganadorGeneral;

Y no olvidemos sus getters y setters:

    public function getGanadorEtapa()
    {
        return $this->ganadorEtapa;
    }

    public function setGanadorEtapa($ganadorEtapa)
    {
        $this->ganadorEtapa = $ganadorEtapa;
    }

    public function getGanadorGeneral()
    {
        return $this->ganadorGeneral;
    }

    public function setGanadorGeneral($ganadorGeneral)
    {
        $this->ganadorGeneral = $ganadorGeneral;
    }

Vaya, cómo está creciendo esta clase.

Bueno, ya lo hemos solucionado por ahora. Pero espera, si la noticia es sobre ciclismo ¿para qué queremos las propiedades $equipo1, $equipo2 y $resultado?

Y al revés, si es de fútbol ¿para qué queremos $ganadorEtapa y $ganadorGeneral y sus correspondientes métodos?

Esto de la programación orientada parece una estafa ¿no?

Resulta que hay una solución a este problema. Podemos crear una clase padre (o clase base) y luego “extenderla” para crear clases hijos (o subclases). La clase padre tendría todas las partes comunes y en cada clase hijo iría solo su código único.

Por ejemplo, la clase base sería algo así:

class Noticia
{
    private $titulo;
    private $cuerpo;
    
    public function getTitulo()
    {
        return $this->titulo;
    }
    
    public function setTitulo($titulo)
    {
        $this->titulo = $titulo;
    }

    public function getCuerpo()
    {
        return $this->cuerpo;
    }

    public function setCuerpo($cuerpo)
    {
        $this->cuerpo = $cuerpo;
    }
}

A partir de esta clase podríamos crear una clase para las noticias de fútbol. Para crear una clase hijo o subclase hacemos ésto:

class NoticiaFutbol extends Noticia

De esta forma creamos una nueva clase NoticiaFutbol que es una subclase de Noticia. Utilizamos la palabra reservada extends para indicar cuál es la clase padre.

Aquí es donde entra en juego el concepto de herencia. Las clases hijo (o sublcases) heredan todas las propiedades y métodos de su padre, en este caso las propiedades $titulo y $cuerpo y sus correspondientes setters y getters.

class NoticiaFutbol extends Noticia
{
    private $equipo1;
    private $equipo2;
    private $resultado;
    
    public function getEquipo1()
    {
        return $this->equipo1;
    }

    public function setEquipo1($equipo1)
    {
        $this->equipo1 = $equipo1;
    }

    public function getEquipo2()
    {
        return $this->equipo2;
    }

    public function setEquipo2($equipo2)
    {
        $this->equipo2 = $equipo2;
    }

    public function getResultado()
    {
        return $this->resultado;
    }

    public function setResultado($resultado)
    {
        $this->resultado = $resultado;
    }
}

Vamos a usarla:

$noticia = new NoticiaFutbol();
$noticia->setTitulo("Gran partidazo");
$noticia->setCuerpo("Jugaron 90 minutos y hubo goles");
$noticia->setEquipo1("Algarrobo FC");
$noticia->setEquipo2("RC Vesarrubia");
$noticia->setResultado("1-0");

Como puedes ver estamos llamando a los métodos setTitulo y setCuerpo que no están en la clase NoticiaFutbol, son unos métodos que ha heredado de su clase padre Noticia.

Los métodos setEquipo1, setEquipo2 y setResultado son métodos específicos de la clase NoticiaFutbol.

Del mismo modo podemos crear la clase NoticiaCiclismo:

class NoticiaCiclismo extends Noticia
{
    private $ganadorEtapa;
    private $ganadorGeneral;

    public function getGanadorEtapa()
    {
        return $this->ganadorEtapa;
    }

    public function setGanadorEtapa($ganadorEtapa)
    {
        $this->ganadorEtapa = $ganadorEtapa;
    }
    
    public function getGanadorGeneral()
    {
        return $this->ganadorGeneral;
    }

    public function setGanadorGeneral($ganadorGeneral)
    {
        $this->ganadorGeneral = $ganadorGeneral;
    }
}

Esta clase también hereda las propiedades y métodos de Noticia.

En resumen, en la clase padre (o clase base) irán todas las propiedades y métodos comunes y en las clases hijo irán sus propiedades y métodos particulares.

La extensión de una clase se usa mucho cuando usamos clases de terceros sin tener que modificarlas. Por ejemplo, imagina que te metes en tu proyecto una clase llamada Email que se usa para enviar emails desde tu aplicación.

Podrías modificarla directamente, pero ¿qué pasará cuando actualices esa clase a una nueva versión? Podrías perder tus cambios o tendrías que estar comparando ambas versiones para ver qué partes modificaste.

Si en lugar de modificar la clase la extiendes y creas, por ejemplo, MiEmail, ya no tendrás que preocuparte al actualizar a una nueva versión de la clase email (salvo que modifiquen su comportamiento de manera que sea incompatible con versiones anteriores).

Todavía quedan algunos temas pendientes relacionados con la herencia:

  • ¿Cómo funciona lo de las propiedades y métodos public, private y protected con clases heredadas?
  • ¿Qué es el overriding?
  • Clases abstractas, interfaces y composición.

Como ves lo divertido no ha hecho más que empezar. En próximos artículos iremos viendo todo esto y mucho más.

Otros capítulos del curso

[postlist id=256]

Leave a Comment