Programação Orientada a Objetos: Herança

 H E R A N Ç A 


Introdução:

Olá corações valentes! Como vão todos? Hoje estarei postando mais um tópico sobre programação, e o assunto da vez é deveras importante: Trata-se do conceito de herança em POO, então não deixe de ler este tópico se deseja saber mais sobre o assunto!


O que é herança?

Através deste conceito, podemos compartilhar e reaproveitar códigos de outras classes, algo muito importante não apenas no desenvolvimento de jogos, mas também no desenvolvimento de aplicações em geral.

Antes de eu salientar do porquê este conceito facilita o nosso trabalho como desenvolvedores, prefiro mostrar um exemplo prático de como seria nossa vida sem a herança:


Digamos que eu esteja programando um jogo e dentro deste jogo eu possua três tipos de inimigos:

Um inimigo que eu vou chamar apenas de "InimigoComum" que recebe o dano do herói e já seria destruído, seu modo de se movimentar pela fase é padrão: aleatório, ele também pode pular obstáculos.

O outro inimigo é um pouquinho mais forte e é apenas destruído quando o herói bater nele algumas vezes, porém, seu jeito de andar também é aleatório e não pula obstáculos.

Por fim, eu tenho um terceiro tipo de inimigo que é destruído apenas com um golpe também, porém a sua maneira de se movimentar é através da perseguição ao herói, também não pula.

Podemos então criar três classes distintas para cada tipo de inimigo:


Inimigo simples:

C #:
public class InimigoComum{
    private int vida = 1;
    public void Andar(){
         Console.WriteLine("Eu ando aleatoriamente");
    }
    
    public void ReceberDano(){
        vida = 0;
    }

public void Pular(){
//Implementação
}
}
Inimigo mais forte:

C #:
public class InimigoRobusto{
    private int vida = 5;
    public void Andar(){
       Console.WriteLine("Eu ando aleatoriamente");
    }
    
    public void ReceberDano(){
        vida--;
    }
}
Inimigo especial:

C #:
public class InimigoPerseguidor{
    private int vida = 1;
    public void Andar(){
        Console.WriteLine("Eu estou perseguindo o herói");
    }
    
    public void ReceberDano(){
        vida = 0;
    }
}
Se você instanciar esses objetos irá de fato criar três tipos de inimigos sem problema algum! Mas perceba que você estaria de certa forma, repetindo código desnecessariamente?
Veja que estamos utilizando métodos e propriedades em comum nas três classes: a propriedade "vida", e os métodos "Andar" e "ReceberDano".
Digamos agora que a classe do inimigo também queria armazenar o nome dele, neste momento você vai ter que ir nas três classes e criar o campo "nome" em todas elas. Pode ser que em três classes isso não dê tanto trabalho, mas e se está lidando com um jogo grande que possui uma quantidade significativa de inimigos? Aí complica, não é?

O problema da repetição de código (ter várias classes com métodos e propriedades em comum "espalhadas" pelo programa) e também a dificuldade da manutenção (como ter de implementar um novo campo nas classes semelhantes) pode ser resolvido a partir do reaproveitamento de código que a programação orientada a objetos nos fornece por meio dos conceitos de herança e polimorfismo.

Um paralelo sobre herança e mundo real:
Agora que foi apresentado o problema vamos à solução: Para contornar nossos problemas de repetição e manutenção, podemos melhorar nosso código e utilizar a herança para compartilhar informações em comum entre as classes.

Quando temos classes que desejam compartilhar informações e ações, podemos chamá-las de "classes de mesmo tipo", assim como acontece no mundo real onde temos tipos e subtipos de espécies animais:
Por exemplo, a espécie "peixe" e seus subtipos: "tubarão", "tilápia", "bagre" e etc. Note que todo "peixe" tem características e comportamentos em comum, como nadar, se alimentar, possuir nadadeiras, guelras, e respiram de baixo d'agua.
Num pensamento mais generalizado, podemos dizer que "peixe" seria o "tipo" desses animais, e as espécies de fato seriam os "subtipos".
E ainda digo mais, apesar deles compartilharem todas essas informações, você concorda comigo que o modo como eles se utilizam de tais propriedades são distintos um do outro? Por exemplo, um tubarão pode nadar muito mais rápido que um bagre, por exemplo, ou o jeito como ambos se alimentam é diferente. Tudo isso os torna espécies (subtipos) distintos.

Implementando a herança em C#:
Trazendo este cenário para nosso problema com os inimigos, podemos então criar um "tipo" chamado "Inimigo" e seus subtipos como "Inimigo comum", "Inimigo robusto" e "inimigo perseguidor".
Em herança, chamamos este "tipo generalizador" de "classe-pai" ou "classe-base", enquanto seus subtipos são "classes-filhas" ou "sub classes".

Vamos encapsular as informações e ações em comum que nossos inimigos compartilham numa classe-base:

C #:
public class Inimigo{
    protected int vida;

    public void Andar(){
       Console.WriteLine("Eu ando aleatoriamente");
    }
    
    public void ReceberDano(){
        vida = 0;
    }
}
Agora temos uma classe que possui a propriedade de "vida" que por padrão inicializa em 1.
Perceba também que este campo está marcado com o modificador de acesso do tipo "protected", isso significa que esse campo poderá ser acesso pela classe que o contém e também pelas suas classes-filhas.
Os métodos "Andar" e "ReceberDano" possuem implementações padrões, ou seja, o inimigo anda aleatoriamente, e ao receber dano é destruído.

Agora posso criar uma sub classe chamada "InimigoComum" e fazer herdá-la de "Inimigo", reaproveitando assim, suas propriedades e métodos:

C #:
public class InimigoComum : Inimigo{
public void Pular(){
// Implementação
}
}
Para realizar a herança em C#, basta utilizar o operador ":" e após ele definir de qual classe quer herdar!


Note que você não precisou implementar os métodos "ReceberDano" ou "Andar", tampouco a propriedade "vida" na classe "InimigoComum", bastou realizar a herança à classe "Inimigo" que essas informações já estão sendo partilhadas à sua sub classe.
Outro ponto importante é que essa classe além de possuir comportamentos e características de sua classe-pai, ainda possui um comportamento único, que seria a ação de poder pular.

Quando você instancia a classe "InimigoComum" veja que você pode ter acesso aos métodos "Andar", e "ReceberDano" que são métodos de sua classe-base, também acesso ao método "Pular" que é exclusivo dessa classe:



Sobrescrita:
Agora para o caso do inimigo robusto, lembra-se que ele inicia com uma quantidade diferente de vida e recebe dano de forma distinta à classe "InimigoComum"?
Para poder reimplementar um comportamento na classe-filha de forma livre, basta utilizar o conceito de sobrescrita (que está atrelado a outro pilar da POO: O polimorfismo, que veremos logo em breve).

Vejamos como esta classe ficaria ao se utilizar do conceito de sobrescrita:

C #:
public class InimigoRobusto : Inimigo{

public InimigoRobusto(int quantidadeDeVida){
this.vida = quantidadeDeVida;
}

public override void ReceberDano(){
vida--;
}
}
Existem algumas coisas que deve-se observar nesta classe:

  • Criamos um construtor que vai definir qual a quantidade de vida para este inimigo, ao invés de deixar "1" por padrão;
  • Utilizamos a palavra-reservada "override" que significa "sobrescrever", para recriar o comportamento de "ReceberDano" da classe-pai, agora dentro da classe "InimigoRobusto" o mesmo decrementa em um sua quantidade de vida, ao invés de ser destruído automaticamente como era no caso padrão.
Utilizamos o "override" em C# sempre que desejamos sobrescrever um comportamento na classe-filha, ou seja, implementando um comportamento próprio em paralelo à classe-pai.

Porém, para que isto funcione corretamente, devemos deixar na classe-pai os métodos que desejamos possivelmente sobrescrever em suas sub classes, como "virtual":

C #:
public class Inimigo{
    protected int vida;

    public virtual  void Andar(){
       Console.WriteLine("Eu ando aleatoriamente");
    }
    
    public virtual void ReceberDano(){
        vida = 0;
    }
}
Por fim, temos agora o último inimigo a ser implementado: o inimigo perseguidor. Lembra-se que este recebe dano padrão, porém o método "Andar" será implementado de forma distinta:

C #:
public class InimigoPerseguidor : Inimigo{
public override void Andar(){
Console.WriteLine("Eu estou perseguindo o herói");
}
}
Agora veja que possuímos as classes dos inimigos que são diferentes, porém, por compartilham informações e ações em comum, pudemos então a partir disto, realizar a herança e o polimorfismo para que pudéssemos implementar de forma mais eficiente as mesmas, evitando a repetição de código desnecessária e melhorando a sua manutenção, mesmo porque, se por exemplo, decidíssemos criar a propriedade "nome" para os inimigos, bastaria declarar ela na classe-pai "Inimigo" e todos seus sub tipos herdariam essa propriedade também.

!Curiosidade
Para chamar o método da classe-base dentro de um método sobrescrito em C#, para realizar as ações do método-base e do método do sub tipo, basta utilizar a seguinte sintaxe:

C #:
public override void Exemplo(){
base.Exemplo();
}



Finalização:
Pois bem pessoal! Aqui chega ao fim mais uma aula sobre C#, porém saiba que o assunto de herança e polimorfismo é bem extenso dentro desta linguagem, portanto ainda teremos outras aulas sobre isso, então te vejo lá!

Comentários