Справочник MQL4

Полиморфизм

Полиморфизм – это возможность для объектов разных классов, связанных с помощью наследования, реагировать различным способом при обращении к одной и той же функции-элементу. Это помогает создавать универсальные механизмы, описывающие поведение не только базового класса, но и классов-потомков.

Продолжим разработку базового класса CShape, в котором определим функцию-член GetArea(), предназначенную для расчета площади фигуры. Во всех классах-потомках, произведенных наследованием от базового класса, мы переопределим эту функцию в соответствие с правилами расчета площади конкретной фигуры.

Для квадрата (класс CSquare) площадь вычисляется через стороны, для круга (класс CCircle) площадь выражается через радиус и так далее. Мы можем создать массив для хранения объектов типа CShape, в котором можно будет хранить как объект базового класса, так и всех его потомков. В дальнейшем мы можем вызывать одну и ту же функцию для любого элемента данного массива.

Пример:

//--- Базовый класс
class CShape
  {
protected
   int            m_type;                // тип фигуры
   int            m_xpos;                // X - координата точки привязки
   int            m_ypos;                // Y - координата точки привязки
public:
   void           CShape(){m_type=0;};   // конструктор, тип равен нулю
   int            GetType(){return(m_type);};// возвращает тип фигуры
virtual
   double         GetArea(){return (0); }// возвращает площадь фигуры
  };

Теперь все производные классы имеют функцию-член getArea(), которая возвращает нулевое значение. Реализация этой функции в каждом потомке будет отличаться.

//--- производный класс Круг
class CCircle : public CShape            // после двоеточия указывается базовый класс,
  {                                      // от которого производится наследование 
private:
   double         m_radius;              // радиус круга
 
public:
   void           CCircle(){m_type=1;};  // конструктор, тип равен 1 
   void           SetRadius(double r){m_radius=r;};
   virtual double GetArea(){return (3.14*m_radius*m_radius);}// площадь круга
  };

Для квадрата объявление класса выглядит аналогично:

//--- производный класс Квадрат
class CSquare : public CShape            // после двоеточия указывается базовый класс,
  {                                      // от которого производится наследование 
private:
   double          m_square_side;        // сторона квадрата
 
public:
   void            CSquare(){m_type=2;}; // конструктор, тип равен 2 
   void            SetSide(double s){m_square_side=s;};
   virtual double  GetArea(){return (m_square_side*m_square_side);}//площадь квадрата
  };

Так как для вычисления площади квадрата и круга требуются соответствующие значения членов m_radius и m_square_side, то в объявлении соответствующего класса мы добавили функции SetRadius() и SetSide().

Предполагается, что в программе у нас используются объекты разного типа (CCircle и CSquare), но унаследованные от одного базового типа CShape. Полиморфизм позволяет нам создать массив объектов базового типа CShape, но при объявлении этого массива сами объекты еще неизвестны и их тип неопределен.

Решение о том, объект какого типа будет содержаться в каждом элементе массива, будет приниматься непосредственно при выполнении программы. Это подразумевает динамическое создание объектов соответствующих классов, и, следовательно, необходимость вместо самих объектов использовать указатели объектов.

Для динамического создания объектов используется оператор new, каждый такой объект нужно самостоятельно и явно удалять оператором delete. Поэтому мы объявим массив указателей типа CShape и для каждого его элемента создадим объект нужного типа (new Имя_класса), как это показано в примере скрипта:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- объявим массив указателей объектов базового типа 
   CShape *shapes[5];   // массив указателей на объекты CShape
 
//--- здесь заполняем массив производными объектами
//--- объявим указатель на объект типа CCircle
   CCircle *circle=new CCircle();
//--- задаем свойства объекта по указателю circle
   circle.SetRadius(2.5);
//--- поместим в shapes[0] значение указателя
   shapes[0]=circle;
 
//--- создаем еще один объект CCircle и запишем его указатель в shapes[1]
   circle=new CCircle();
   shapes[1]=circle;
   circle.SetRadius(5);
 
//--- тут мы намеренно "забыли" задать значение для shapes[2]
//circle=new CCircle();
//circle.SetRadius(10);
//shapes[2]=circle;
 
//--- для неиспользуемого элемента установим значение NULL
   shapes[2]=NULL;
 
//--- создаем объект CSquare и запишем его указатель в shapes[3]
   CSquare *square=new CSquare();
   square.SetSide(5);
   shapes[3]=square;
 
//--- создаем объект CSquare и запишем его указатель в shapes[4]
   square=new CSquare();
   square.SetSide(10);
   shapes[4]=square;
 
//--- массив указателей есть, получим его размер
   int total=ArraySize(shapes);
//--- пройдем в цикле по всем указателям в массиве 
   for(int i=0; i<5;i++)
     {
      //--- если по указанному индексу указатель является валидным
      if(CheckPointer(shapes[i])!=POINTER_INVALID)
        {
         //--- выведем в лог тип и площадь фигуры
         PrintFormat("Объект типа %d имеет площадь %G",
               shapes[i].GetType(),
               shapes[i].GetArea());
        }
      //--- если указатель имеет тип POINTER_INVALID
      else
        {
         //--- сообщим об ошибке
         PrintFormat("Объект shapes[%d] не инициализирован! Его указатель %s",
                     i,EnumToString(CheckPointer(shapes[i])));
        }
     }
 
//--- мы должны самостоятельно уничтожить все созданные динамические объекты
   for(int i=0;i<total;i++)
     {
      //--- удалять можно только объекты, чей указатель имеет тип POINTER_DYNAMIC
      if(CheckPointer(shapes[i])==POINTER_DYNAMIC)
        {
         //--- сообщим об удалении
         PrintFormat("Удаляем shapes[%d]",i);
         //--- уничтожим объект по его указателю
         delete shapes[i];
        }
     }
  }

Обратите внимание, что при уничтожении объекта оператором delete необходимо проверить тип его указателя. Удалять с помощью delete можно только объекты, имеющие указатель POINTER_DYNAMIC, для указателей другого типа будет получена ошибка.

Кроме переопределения функции при наследовании, полиморфизм включает в себя также и реализацию одной и той же функции с разным набором параметров в пределах одного класса. Это означает, что в классе может быть определено несколько функций с одним и тем же именем, но с разным типом и/или набором параметров. В этом случае полиморфизм реализуется через перегрузку функций.