Herança

O principal característica da OOP é o incentivo à reutilização de código através de herança. Uma nova classe é feita a partir de uma já existente, que é chamada de classe base. A classe derivada usa os membros da classe base, mas também pode modificar e complementá-los.

Muitos tipos são variações de tipos já existentes. É muitas vezes tedioso desenvolver um novo código para cada um deles. Além disso, um novo código implica em novos erros. A classe derivada herda a descrição da classe base, assim qualquer re-desenvolvimento e re-teste de código é desnecessário. As relações de herança são hierárquicas.

Hierarquia é um método que permite copiar os elementos em toda a sua diversidade e complexidade. Ela introduz a classificação de objetos. Por exemplo, a tabela periódica de elementos tem gases. Eles possuem propriedades inerentes a todos os elementos periódicos.

Gases inertes constituem a próxima importante subclasse. A hierarquia é que o gás inerte, como árgon, é um gás, e um gás, por sua vez, é parte do sistema. Tal hierarquia permite interpretar o comportamento dos gases inertes com facilidade. Sabemos que seus átomos contêm prótons e elétrons, o que é verdade para todos os outros elementos.

Sabemos que eles estão no estado gasoso à temperatura ambiente, como todos os gases. Sabemos que nenhum gás da subclasse de gases inertes entra usualmente em reações químicas com outros elementos, e isso é uma propriedade de todos os gases inertes.

Considere um exemplo de herança de formas geométricas. Para descrever a completa variedade de formas simples (círculos, triângulos, retângulos, quadrados, etc.), a melhor forma é criar uma classe base (ADT), que é o predecessor de todas as classes derivadas.

Vamos criar uma classe base CShape, que contém apenas a maioria dos membros comuns que descrevem a forma. Estes membros descrevem propriedades que são características de qualquer forma - o tipo da forma e as coordenadas do ponto de ancoragem principal.

Exemplo:

//--- A classe base da forma (Shape)
class CShape
  {
protected:
   int       m_type;                   // Tipo de Forma
   int       m_xpos;                   // X - coordenada do ponto base
   int       m_ypos;                   // Y - coordenada do ponto base
public:
             CShape(){m_type=0; m_xpos=0; m_ypos=0;} // construtor
   void      SetXPos(int x){m_xpos=x;} // define X
   void      SetYPos(int y){m_ypos=y;} // define Y
  };

Sem seguida, criamos novas classes derivadas da classe base, nas quais adicionaremos campos necessários, cada um especificando uma certa classe. Para a forma Círculo, é necessário adicionar um membro que contém o valor do raio. A forma Quadrado é caracterizada pelo valor do lado. Portanto, classes derivadas, herdadas da classe base CShape, serão declaradas como se segue:

//--- A classe derivada círculo (Circle)
class CCircle : public CShape        // Depois de um dois pontos, nós definimos a classe base
  {                                    // a partir da qual a herança é feita
private:
   int             m_radius;           // raio do círculo
 
public:
                   CCircle(){m_type=1;}// construtor, tipo 1 
  };

Para a classe do Quadrado, a declaração é semelhante:

//--- a classe derivada quadrado (Square)
class CSquare : public CShape        // Depois de um dois pontos, nós definimos a classe base
  {                                    // a partir da qual a herança é feita
private:
   int            m_square_side;       // lado do quadrado
 
public:
                  CSquare(){m_type=2;} // construtor, tipo 2 
  };

Deve-se notar que enquanto um objeto é criado, o construtor da classe base é chamado primeiro, e então o construtor da classe derivada é chamado. Quando um objeto é destruído, primeiro o destrutor da classe derivada é chamado, e então o destrutor da classe base é chamado.

Assim, ao declarar a maioria do membros gerais na classe base, podemos acrescentar membros adicionais nas classes derivadas que especificam uma classe particular. A herança permite criar bibliotecas de código poderosas que podem ser reutilizadas muitas vezes.

A sintaxe para criar uma classe derivada a partir de uma classe já existente é a seguinte:

class class_name : 
          (public | protected | privateopt  base_class_name
  {                                    
   declaração de membros de classe
  };

 

Um dos aspectos da classe derivada é a visibilidade (abertura) de seus sucessores membros (herdeiros). As palavras-chave public, protected e private são usadas para indicar quão disponíveis os membros da classe base estarão disponíveis para a classe derivada. A palavra-chave public após um dois pontos no cabeçalho de uma classe derivada indica que os membros protegidos e públicos da classe base CShape devem ser herdados como membros protegidos e públicos da classe derivada CCircle.

Os membros privados da classe base não são disponíveis para a classe derivada. A herança pública também significa que classes derivadas (CCircle e CSquare) são CShapes. Isto é, o Quadrado (CSquare) é uma Forma (CShape), mas a Forma não necessariamente tem que ser um Quadrado.

A classe derivada é uma modificação da classe base, ele herda os membros protegidos e públicos da classe base. Os construtores e destrutores da classe base não podem ser herdados. Além de membros da classe base, novos membros são adicionados em uma classe derivada.

A classe derivada pode incluir a implementação de funções membro, diferentes da classe base. Isso não tem nada a ver com uma sobrecarga, quando o significado de um mesmo nome de uma função pode ser diferente para diferentes assinaturas.

Em herança protegida, membros públicos e protegidos da classe base tornam-se membros protegidos da classe derivada. Em herança privada, os membros públicos e protegidos da classe base tornam-se membros privados da classe derivada.

Em herança protegida e privada, a relação "o objeto de uma classe derivada é objeto da classe base" não é verdade. Os tipos protegido e privado de herança são raros, e cada um deles precisam ser usados com cautela.

Deve ser entendido que o tipo de herança (public, protected ou private) não afeta a forma de acessar os membros de classes base na hierarquia de herança a partir de uma classe derivada. Para qualquer tipo de herança, somente membros da classe base declarados com especificadores de acesso public e protected estarão disponíveis fora das classes derivadas. Vamos verificar isso no seguinte exemplo:

#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Exemplo de classe com alguns tipos de acesso                     |
//+------------------------------------------------------------------+
class CBaseClass
  {
private:             //--- O membro privado não é disponível a partir de classe derivada
   int               m_member;
protected:           //--- O método protegido é disponível a partir da classe base e suas classes derivadas
   int               Member(){return(m_member);}
public:              // O construtor de classe é disponível para todos os membros
                     CBaseClass(){m_member=5;return;};
private:             //--- Um método particular para atribuir um valor para m_member
   void              Member(int value) { m_member=value;};
 
  };
//+------------------------------------------------------------------+
//| Classe derivada com erros                                        |
//+------------------------------------------------------------------+
class CDerived: public CBaseClass // especificação da herença pública pode ser omitido, uma vez que é predefinido
  {
public:
   void Func() // Na classe derivada, defina uma função com chamadas aos membros da classe base
     {
      //--- Uma tentativa de alterar um membro privado da classe base
      m_member=0;        // Erro, o membro privado da classe base não é disponível
      Member(0);         // Erro, o método privado da classe base não é disponível em classes derivadas
      //--- Leitura do membro da classe base
      Print(m_member);   // Erro, o membro privado da classe base não é disponível
      Print(Member());   // Sem erro, método protegido é acessível a partir da classe base e suas classes derivadas
     }
  };

No exemplo acima, CBaseClass tem apenas um método público - o construtor. Construtores são chamados automaticamente na criação de um objeto de classe. Portanto, o membro privado m_member e o método protegido Member() não podem ser chamados do lado de fora. Mas no caso de herança pública, o membro Member() da classe base estará disponível a partir de classes derivadas.

No caso de herança protegida, todos os membros da classe base com acessos público e protegido tornam-se protegidos. Isso significa que membros de dados e métodos públicos da classe base, com herança protegida eles passam a ser disponíveis somente a partir de classes derivadas e de suas derivadas seguintes.

//+------------------------------------------------------------------+
//| Exemplo de classe com alguns tipos de acesso                     |
//+------------------------------------------------------------------+
class CBaseMathClass
  {
private:             //--- O membro privado não é disponível a partir de classe derivada
   double            m_Pi;
public:              //--- Obtendo e definindo um valor para m_Pi
   void              SetPI(double v){m_Pi=v;return;};
   double            GetPI(){return m_Pi;};
public:              // O construtor de classe é disponível para todos os membros
                     CBaseMathClass() {SetPI(3.14);  PrintFormat("%s",__FUNCTION__);};
  };
//+------------------------------------------------------------------+
//| Uma classe derivada, em que m_Pi não pode ser modificada         |
//+------------------------------------------------------------------+
class CProtectedChildClass: protected CBaseMathClass // Herança protegida
  {
private:
   double            m_radius;
public:              //--- Métodos públicos na classe derivada
   void              SetRadius(double r){m_radius=r; return;};
   double            GetCircleLength(){return GetPI()*m_radius;};
  };
//+------------------------------------------------------------------+
//| Função de inicialização de script                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Ao criar uma classe derivada, o construtor da classe base será chamada automaticamente
   CProtectedChildClass pt;
//--- Especifica o raio
   pt.SetRadius(10);
   PrintFormat("Length=%G",pt.GetCircleLength());
//--- Se comentar a string abaixo, obteremos um erro na etapa de compilação, já que SetPi() é agora protegida
// pt.SetPI(3); 
 
//--- Agora declare um variável da classe base e tente definir a constante Pi igual a 10
   CBaseMathClass bc;
   bc.SetPI(10);
//--- Aqui está o resultado
   PrintFormat("bc.GetPI()=%G",bc.GetPI());
  }

O exemplo mostra que os métodos SetPI() e GetPI() na classe base CBaseMathClasse estão abertos e disponíveis para chamadas a partir de qualquer lugar do programa. Mas ao mesmo tempo, para CProtectedChildClasse, que é derivada dela, estes métodos podem ser chamados somente a partir de métodos da classe CProtectedChildClass ou suas classes derivadas.

No caso de herança privada, todos os membros da classe base com acesso público e protegido tornam-se privados, e chamá-los torná-se impossível em herança posterior.

MQL5 não tem herança múltipla.

Também Veja

Estruturas e Classes