36 Herencia

Una de las principales propiedades de las clases es la herencia. Esta propiedad nos permite crear nuevas clases a partir de clases existentes, conservando las propiedades de la clase original y añadiendo otras nuevas.

Jerarquía, clases base y clases derivadas

Cada nueva clase obtenida mediante herencia se conoce como clase derivada, y las clases a partir de las cuales se deriva, clases base. Además, cada clase derivada puede usarse como clase base para obtener una nueva clase derivada. Y cada clase derivada puede serlo de una o más clases base. En este último caso hablaremos de derivación múltiple.

Esto nos permite crear una jerarquía de clases tan compleja como sea necesario.

Bien, pero ¿que ventajas tiene derivar clases?

En realidad, ese es el principio de la programación orientada a objetos. Esta propiedad nos permite encapsular diferentes partes de cualquier objeto real o imaginario, y vincularlo con objetos más elaborados del mismo tipo básico, que heredarán todas sus características. Lo veremos mejor con un ejemplo.

Un ejemplo muy socorrido es de las personas. Supongamos que nuestra clase base para clasificar a las personas en función de su profesión sea "Persona". Presta especial atención a la palabra "clasificar", es el punto de partida para buscar la solución de cualquier problema que se pretenda resolver usando POO. Lo primero que debemos hacer es buscar categorías, propiedades comunes y distintas que nos permitan clasificar los objetos, y crear lo que después serán las clases de nuestro programa. Es muy importante dedicar el tiempo y atención necesarios a esta tarea, de ello dependerá la flexibilidad, reutilización y eficacia de nuestro programa.

Ten en cuenta que las jerarquías de clases se usan especialmente en la resolución de problemas complejos, es difícil que tengas que recurrir a ellas para resolver problemas sencillos.

Siguiendo con el ejemplo, partiremos de la clase "Persona". Independientemente de la profesión, todas las personas tienen propiedades comunes, nombre, fecha de nacimiento, género, estado civil, etc.

Estructura de clases para Persona
Jerarquía de clases para Persona

La siguiente clasificación debe ser menos general, supongamos que dividimos a todas las personas en dos grandes clases: empleados y estudiantes. (Dejaremos de lado, de momento, a los estudiantes que además trabajan). Lo importante es decidir qué propiedades que no hemos incluido en la clase "Persona" son exclusivas de los empleados y de los estudiantes. Por ejemplo, los ingresos por nómina son exclusivos de los empleados, la nota media del curso, es exclusiva de los estudiantes. Una vez hecho eso crearemos dos clases derivadas de Persona: "Empleado" y "Estudiante".

Haremos una nueva clasificación, ahora de los empleados. Podemos clasificar a los empleados en ejecutivos y comerciales (y muchas más clases, pero para el ejemplo nos limitaremos a esos dos). De nuevo estableceremos propiedades exclusivas de cada clase y crearemos dos nuevas clases derivadas de "Empleado": "Ejecutivo" y "Comercial".

Ahora veremos las ventajas de disponer de una jerarquía completa de clases.

  • Cada vez que creemos un objeto de cualquier tipo derivado, por ejemplo de tipo Comercial, estaremos creando en un sólo objeto un Comercial, un Empleado y una Persona. Nuestro programa puede tratar a ese objeto como si fuera cualquiera de esos tres tipos. Es decir, nuestro comercial tendrá, además de sus propiedades como comercial, su nómina como empleado, y su nombre, edad y género como persona.
  • Siempre podremos crear nuevas clases para resolver nuevas situaciones. Consideremos el caso de que en nuestra clasificación queremos incluir una nueva clase "Becario", que no es un empleado, ni tampoco un estudiante; la derivaríamos de Persona. También podemos considerar que un becario es ambas cosas. Sería un ejemplo de derivación múltiple, podríamos hacer que la clase derivada Becario, lo fuera de Empleado y Estudiante.
  • Podemos aplicar procedimientos genéricos a una clase en concreto, por ejemplo, podemos aplicar una subida general del salario a todos los empleados, independientemente de su profesión, si hemos diseñado un procedimiento en la clase Empleado para ello.

Veremos que existen más ventajas, aunque este modo de diseñar aplicaciones tiene también sus inconvenientes, sobre todo si diseñamos mal alguna clase.

Derivar clases, sintaxis

La forma general de declarar clases derivadas es la siguiente:

class <clase_derivada> : 
   [public|private] <base1> [,[public|private] <base2>] {};

En seguida vemos que para cada clase base podemos definir dos tipos de acceso, public o private. Si no se especifica ninguno de los dos, por defecto se asume que es private.

  • public: los miembros heredados de la clase base conservan el tipo de acceso con que fueron declarados en ella.
  • private: todos los miembros heredados de la clase base pasan a ser miembros privados en la clase derivada.

De momento siempre declararemos las clases base como public, al menos hasta que veamos la utilidad de hacerlo como privadas.

Veamos un ejemplo sencillo basado en la idea del punto anterior:

// Clase base Persona:
class Persona {
  public:
   Persona(char *n, int e);
   const char *LeerNombre(char *n) const;
   int LeerEdad() const;
   void CambiarNombre(const char *n);
   void CambiarEdad(int e);
   
  protected:
   char nombre[40];
   int edad;
};
 
// Clase derivada Empleado:
class Empleado : public Persona {
  public:
   Empleado(char *n, int e, float s);
   float LeerSalario() const;
   void CambiarSalario(const float s);
   
  protected:
   float salarioAnual;
};

Podrás ver que hemos declarado los datos miembros de nuestras clases como protected. En general es recomendable declarar siempre los datos de nuestras clases como privados, de ese modo no son accesibles desde el exterior de la clase y además, las posibles modificaciones de esos datos, en cuanto a tipo o tamaño, sólo requieren ajustes de los métodos de la propia clase.

Pero en el caso de estructuras jerárquicas de clases puede ser interesante que las clases derivadas tengan acceso a los datos miembros de las clases base. Usar el acceso protected nos permite que los datos sean inaccesibles desde el exterior de las clases, pero a la vez, permite que sean accesibles desde las clases derivadas.