Polimorfismo

Polimorfismo es una posibilidad para objetos de diferentes clases vinculados por medio de la herencia reaccionar de varias maneras a la hora de dirigirse a la misma función-elemento.  Eso permite crear unos mecanismos universales que describen el comportamiento no sólo de la clase base, sino el de las clases descendentes.

Vamos a seguir desarrollando la clase base CShape donde definimos la función miembro GetArea() que sirve para calcular la superficie de una figura. En todas las clases descendientes de la clase base vamos a redefinir esta función de acuerdo con las normas de cálculo de la superficie de una figura en concreto.

Para el cuadrado (clase CSquare) la superficie se calcula por los lados, para el círculo (clase CCircle) la superficie se calcula por el radio y etc. Podemos crear un array para almacenar los objetos del tipo CShape en el que podremos almacenar tanto el objeto de la clase base, como el de todos sus descendientes. En el futuro podremos llamar a la misma función para cualquier elemento de este array.

Ejemplo:

//--- Clase base
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:
   void           CShape(){m_type=0;};       // constructor, el tipo es igual a cero
   int            GetType(){return(m_type);};// devuelve el tipo de figura
virtual
   double         GetArea(){return (0); }    // devuelve la superficie de figura
  };

Ahora todas las clases derivadas tienen la función miembro getArea() que devuelve el valor cero. La implementación de esta función va a ser distinta en cada un de los descendientes.

//--- 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:
   double         m_radius;                 // radio del círculo
 
public:
   void           CCircle(){m_type=1;};     // constructor, el tipo es igual a 1 
   void           SetRadius(double r){m_radius=r;};
   virtual double GetArea(){return (3.14*m_radius*m_radius);}// superficie del círculo
  };

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:
   double          m_square_side;          // lado del cuadrado
 
public:
   void            CSquare(){m_type=2;};   // constructor, el tipo es igual a 2 
   void            SetSide(double s){m_square_side=s;};
   virtual double GetArea(){return (m_square_side*m_square_side);}//superficie del cuadrado
  };

Como para el cálculo de la superficie del círculo y cuadrado se necesitan los valores correspondientes de los miembros m_radius y m_square_side, entonces en la declaración de la clase correspondiente hemos añadido las funciones SetRadius y SetSide().

Se supone que en nuestro programa se utilizan los objetos de deferentes tipos (CCircle y CSquare) pero heredados de un tipo base CShape. El polimorfismo no permite crear un array de objetos del tipo base CShape pero durante la declaración de este array los objetos aún no se conoces y su tipo no está definido.

La decisión sobre el objeto de qué tipo va a contenerse en cada elemento del array va a tomarse directamente en el proceso de ejecución del programa. Esto supone la creación dinámica de objetos de las clases correspondientes, y por consecuencia, la necesidad de usar los punteros a objetos en vez de los objetos en sí.

Para la creación dinámica de los objetos se utiliza el operador new. Cada objeto tiene que eliminarse de manera individual y explícita con el operador delete. Por eso declararemos un array de punteros del tipo CShape, y crearemos para cada elemento suyo un objeto del tipo necesario (new Nombre _de_la_clase), tal como se muestra en el ejemplo del script:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- declararemos un array de punteros del tipo base
   CShape *shapes[5];   // array de punteros a objetos CShape
 
//--- aquí llenamos el array con objetos derivados
//--- declararemos un puntero al objeto del tipo CCircle
   CCircle *circle=new CCircle();
//--- establecemos las propiedades del objeto según el puntero circle
   circle.SetRadius(2.5);
//--- colocaremos el valor del puntero en shapes[0]
   shapes[0]=circle;
 
//--- crearemos otro objeto CCircle más y escribiremos su puntero en shapes[1]
   circle=new CCircle();
   shapes[1]=circle;
   circle.SetRadius(5);
 
//--- aquí "hemos olvidado" establecer aposta el valor para shapes[2]
//circle=new CCircle();
//circle.SetRadius(10);
//shapes[2]=circle;
 
//--- estableceremos el valor NULL para el elemento que no se utiliza
   shapes[2]=NULL;
 
//--- crearemos el objeto CSquare y escribiremos su puntero en shapes[3]
   CSquare *square=new CSquare();
   square.SetSide(5);
   shapes[3]=square;
 
//--- crearemos el objeto CSquare y escribiremos su puntero en shapes[4]
   square=new CSquare();
   square.SetSide(10);
   shapes[4]=square;
 
//--- tenemos un array de punteros, obtendremos su tamaño
   int total=ArraySize(shapes);
//--- pasaremos en un ciclo por todos los punteros en el array
   for(int i=0; i<5;i++)
     {
      //--- si el puntero en el índice especificado es válido
      if(CheckPointer(shapes[i])!=POINTER_INVALID)
        {
         //--- mostraremos en el log el tipo y la superficie del figura
         PrintFormat("El objeto del tipo %d tiene la superficie %G",
               shapes[i].GetType(),
               shapes[i].GetArea());
        }
      //--- si el puntero tiene el tipo POINTER_INVALID
      else
        {
         //--- avisaremos sobre el error
         PrintFormat("¡El objeto shapes[%d] no ha sido inicializado! Su puntero es %s",
                     i,EnumToString(CheckPointer(shapes[i])));
        }
     }
 
//--- tenemos que eliminar personalmente todos los objetos dinámicos creados
   for(int i=0;i<total;i++)
     {
      //--- se puede eliminar sólo los objetos cuyo puntero tiene el tipo POINTER_DYNAMIC
      if(CheckPointer(shapes[i])==POINTER_DYNAMIC)
        {
         //--- avisaremos sobre la eliminación
         PrintFormat("Eliminamos shapes[%d]",i);
         //--- eliminaremos los objetos según su puntero
         delete shapes[i];
        }
     }
  }

Preste la atención a que cuando se elimina un objeto por el operador delete, hay que comprobar el tipo de su puntero. A través de delete se puede eliminar sólo los objetos que tienen los punteros POINTER_DYNAMIC. Para los punteros de otros tipos se mostrará el error.

Pero a parte de la redefinición de la función durante la herencia, el polimorfismo incluye también la implementación de la misma función con diferentes conjuntos de parámetros dentro de los límites de una clase. Eso significa que la clase puede tener varias funciones con el mismo nombre pero de diferentes tipos y/o conjunto de parámetros. En este caso el polimorfismo se implementa a través de la sobrecarga de funciones.

Véase también

Biblioteca estándar