Herencia

La particularidad de POO es la promoción de reuso del código utilizando los mecanismos de herencia. Una clase nueva se crea de una ya existente que se llama la clase base (superclase). La clase derivada (subclase) utiliza los elementos de la clase base. La clase derivada usa los elementos de la clase base, aunque también puede modificar y completarlos.

Muchos tipos son variaciones de los temas ya existentes. A menudo resulta muy agotador elaborar un código nuevo para cada uno de ellos. Además, un código nuevo supone errores nuevos. La clase derivada hereda la descripción de la clase base, siendo innecesario volver a crear y comprobar el código. Las relaciones de herencia se representan en una forma jerárquica.

La jerarquía es un método que permite copiar los elementos en toda su diversidad y complejidad. Ella introduce la clasificación de objetos. Por ejemplo, en la tabla periódica de los elementos hay gases. Ellos poseen propiedades inherentes a todos los elementos del sistema.

Los gases inertes es la siguiente subclase importante. La jerarquía consiste en que el gas inerte, como por ejemplo el argón, es un gas, y en su lugar el gas es un elemento del sistema. Este tipo de jerarquía permite explicar fácilmente el comportamiento de los gases inertes. Sabemos que sus átomos contienen protones y electrones, lo que es cierto para los demás elementos.

Sabemos que se encuentran en el estado gaseoso con la temperatura interior, igual que todos los gases. Sabemos que ningún gas de la subclase de gases inertes entra en la reacción química común con ninguno de los elemento, y esta es la propiedad de todos los gases inertes.

Vamos a ver la jerarquía en el ejemplo de las figuras geométricas. Para describir la variedad de figuras simples (círculo, triángulo, rectángulo, cuadrado, etc.) es mejor crear una clase base (TDA), que resulta ser el antecesor de todas las clases derivadas.

Vamos a crear la clase base CShape que contiene sólo los miembros más comunes que describen la figura. Estos miembros describen las propiedades características de cualquier figura, es decir, tipo de figura y las coordenadas del punto principal de enlace.

Ejemplo:

//--- Clase base Figura
class CShape
  {
protected:
   int       m_type;                   // tipo de figura
   int       m_xpos;                   // X - coordenada del punto de enlace
   int       m_ypos;                   // Y - coordenada del punto de enlace
public:
             CShape(){m_type=0; m_xpos=0; m_ypos=0;} // constructor
   void      SetXPos(int x){m_xpos=x;} // establezcamos X
   void      SetYPos(int y){m_ypos=y;} // establezcamos Y
  };

Luego vamos a crear nuevas clases derivadas de la clase base en las cuales vamos a añadir campos necesarios que especifican cada clase en particular. Para la figura Circle(círculo) hace falta añadir un miembro que contiene el valor del radio. La figura Quadrate (cuadrado) se caracteriza por el valor del lado del cuadrado. Por lo tanto, las clases derivadas, heredadas de la clase base CShape, serán declaradas como sigue:

//--- clase derivada Círculo
class CCircle : public CShape        // después de dos puntos definimos la clase base
  {                                    // de la que se hace la herencia 
private:
   int             m_radius;           // radio del círculo
 
public:
                   CCircle(){m_type=1;}// constructor, el tipo es igual a 1 
  };

Para el cuadrado la declaración de la clase es similar:

//--- clase derivada Cuadrado
class CSquare : public CShape        // después de dos puntos definimos la clase base
  {                                    // de la que se hace la herencia 
private:
   int            m_square_side;       // lado del cuadrado
 
public:
                  CSquare(){m_type=2;} // constructor, el tipo es igual a 2 
  };

Cabe señalar que durante la creación de un objeto primero se llama al constructor de la clase base, y luego al constructor de la clase derivada. Cuando se destruye un objeto, primero se llama al destructor de la clase derivada, y luego al destructor de la clase base.

De esa manera, al declarar en la clase base los miembros más generales, podemos añadir miembros adicionales en las clases derivadas, los que precisan una clase en concreto. La herencia permite crear potentes bibliotecas de código que pueden ser utilizadas varias veces y en repetidas ocasiones.

La sintaxis de crear una clase derivada de una ya existente es como sigue:

class nombre_de_clase : 
          (public | protected | privateopt  nombre_de_clase_base
  {                                    
    declaración de miembros
  };

Uno de los aspectos de una clase derivada es la visibilidad (abertura) de sus miembros-herederos. Las palabras claves public, protected y private se utilizan para indicar el nivel de acceso de los elementos de la clase base para la clase derivada. El uso de la palabra clave private seguida de dos puntos en el encabezado de la clase derivada significa que los miembros protegidos y abiertos (protected y public) de la clase base CShape deberían ser heredados como elementos protegidos y abiertos de la clase derivada CCircle.

Los miembros privados de la clase base no están disponibles para la clase derivada.  La herencia pública también significa que las clases derivadas (CCircle y CSquare) son CShape. Es decir, el cuadrado (CSquare) es la figura (CShape) pero la figura no tiene que ser obligatoriamen6te un cuadrado.

La clase derivada es una modificación de la clase base; hereda los miembros protegidos y públicos de la clase base. Los constructores y destructores de la clase base no pueden ser heredados. A menudo a la clase derivada se añaden nuevos miembros como complemento a los miembros de la clase base.

La clase derivada puede incluir la implementación de las funciones-miembro diferente de la clase base. Eso no tiene nada que ver con la sobrecarga cuando el significado del nombre de la función puede ser distinto para diferentes signaturas.

Con la herencia protegida los miembros públicos y protegidos de la clase base se hacen miembros protegidos de la clase derivada. Con la herencia privada los miembros públicos y protegidos de la clase base se hacen miembros privados de la clase derivada.

Con la herencia protegida y privada la relación de que "un objeto de clase derivada es un objeto de clase base" no es cierta. La herencia protegida y privada se encuentran muy raras veces, y hay que usar cada una de ellas con mucho cuidado.

Hay que entender que el tipo de herencia (public, protected o private) de ninguna manera influye en los modos de acceso a los miembros de las clases base en la jerarquía de la herencia desde una clase derivada (un heredero). Sea como sea el tipo de herencia, desde las clases derivadas estarán disponibles sólo los miembros de la clase base declarados con los especificadores de acceso public y protected. Veremos lo arriba mencionado en un ejemplo:

#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Una clase de ejemplo con varios tipos de acceso                        |
//+------------------------------------------------------------------+
class CBaseClass
  {
private:             //--- el miembro privado no está disponible desde las clases derivadas
   int               m_member;
protected:           //--- el modo protegido está disponible desde la clase base y sus clases derivadas
   int               Member(){return(m_member);}
public:              // el constructor de la clase está disponible para todos
                     CBaseClass(){m_member=5;return;};
private:             // el modo cerrado para asignar valores al miembro m_member
   void              Member(int value) { m_member=value;};
 
  };
//+------------------------------------------------------------------+
//|  clase derivada con errores                                    |
//+------------------------------------------------------------------+
class CDerivaed: public CBaseClass // se puede omitir la especificación de la herencia public, es por defecto
  {
public:
   void Func() // definimos en la clase derivada una función con las llamadas a los miembros de la clase base 
     {
      //--- intento de modificación de un miembro privado de la clase base
      m_member=0;        // error, el miembro privado de la clase base no está disponible para nadie
      Member(0);         // error, el método privado de la clase base no está disponible en las clases derivadas
      //--- lectura del miembro de la clase base
      Print(m_member);   // error, el miembro privado de la clase base no está disponible para nadie
      Print(Member());   // no hay error, el método público de la clase base está disponible siempre y para todos
     }
  };

En el ejemplo citado, la clase CBaseClass tiene sólo un método público, es el constructor. Los constructores se llaman automáticamente cuando se crea un objeto de la clase. Por eso no hay ninguna manera de llamar al miembro privado m_member, ni tampoco al método protegido Member() desde fuera. Pero en caso de la herencia pública (public), el método Member() de la clase base estará disponible desde las clases derivadas.

En caso de la herencia protegida (protected), todos los miembros de la clase base con el acceso público y protegido se hacen protegidos. Esto significa que si los miembros-datos y métodos públicos de la clase base estaban disponibles desde fuera, entonces ahora en caso de la herencia protegida ellos estarán disponibles sólo desde las clases del heredero y de sus posteriores clases derivadas.

//+------------------------------------------------------------------+
//| Una clase de ejemplo con varios tipos de acceso                        |
//+------------------------------------------------------------------+
class CBaseMathClass
  {
private:             //--- el miembro privado no está disponible desde las clases derivadas
   double            m_Pi;
public:              //--- obtención y establecimiento del parámetro para m_Pi
   void              SetPI(double v){m_Pi=v;return;};
   double            GetPI(){return m_Pi;};
public:              // el constructor de la clase está disponible para todos
                     CBaseMathClass() {SetPI(3.14);  PrintFormat("%s",__FUNCTION__);};
  };
//+------------------------------------------------------------------+
//| Una clase derivada en la que ya no se puede modificar m_Pi            |
//+------------------------------------------------------------------+
class CProtectedChildClass: protected CBaseMathClass // herencia protegida
  {
private:
   double            m_radius;
public:              //--- métodos públicos en la clase derivada
   void              SetRadius(double r){m_radius=r; return;};
   double            GetCircleLength(){return GetPI()*m_radius;};
  };
//+------------------------------------------------------------------+
//|  Función del inicio del script                                         |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- durante la creación de una clase derivada, el constructor de la clase base se llama automáticamente
   CProtectedChildClass pt;
//--- especificamos el radio
   pt.SetRadius(10);
   PrintFormat("Length=%G",pt.GetCircleLength());
//--- si comentamos la cadena de abajo, obtendremos el error en la fase de compilación, porque el método SetPi() se ha hecho protegido
// pt.SetPI(3); 
 
//--- ahora declararemos una variable de la clase base e intentaremos establecer la constante Pi igual a 10
   CBaseMathClass bc;
   bc.SetPI(10);
//--- veremos lo que ha salido
   PrintFormat("bc.GetPI()=%G",bc.GetPI());
  }

En este ejemplo se muestra que los métodos SetPI() y GetPi() en la clase base CBaseMathClass son públicos y están disponibles para la llamada desde cualquier parte del programa. Pero al mismo tiempo, para su derivada CProtectedChildClass las llamadas a estos métodos se puede realizar sólo desde los métodos de la misma clase CProtectedChildClass o sus clases derivadas.

En caso de la herencia privada (private), todos los miembros de la clase base con el acceso public y protected se hacen privados, y en caso de posteriores herencias resulta imposible llamarlos.

En MQL5 no hay herencia múltiple.

Véase también

Estructuras y clases