🤔 Para Refletir :
"Por que tão sérios? Não... eu diria: por que tão perfeccionistas?"
- Eliyud

Programação orientada a objetos: Herança

Mayleone Feminino

Conde
Membro
Membro
Console.Write("Hello World!");
Juntou-se
25 de Outubro de 2016
Postagens
267
Bravecoins
3.095
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?
Um dos pilares da programação orientada a objetos, como já dito neste tópico, é a 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:

Bw7x6Cu.png


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á!
 
Seria estranho se eu não achasse um tutorial seu incrível, sendo que sou seu fã a anos! Parabéns por mais um tutorial super didático! Agora eu entendo, de uma vez por todas, o que é a herança e como aplicar! Você nunca mais vai ver a variável vida várias vezes em vários códigos diferentes nos meus jogos! HAHAHA

Obrigado por no dar mais uma excelente aula! * . *
 

possoopinar.gif

Professora @Mayleone, perguntinha de uma aluna leiga aqui...

Quer dizer então que uma classe utilizada como herança pode sofrer alterações para novas criações a partir da "base" que ela carrega?
Em resumo: Objetos com classes semelhantes podem possuir comportamentos distintos? É isso?
Mesmo se eu estiver falando besteira, saiba que gosto muito da forma interativa que explica hahahá!
 
1. Quer dizer então que uma classe utilizada como herança pode sofrer alterações para novas criações a partir da "base" que ela carrega?

2. Em resumo: Objetos com classes semelhantes podem possuir comportamentos distintos? É isso?
Mesmo se eu estiver falando besteira, saiba que gosto muito da forma interativa que explica hahahá!

1: Sim, você pode sobreescrever métodos (override) e criar novos

2: Classes com mesma classe pai podem ter comportamento distintos, por exemplo, todo inimigo ataca, porém o inimigo mais forte ataca de um jeito diferente (sobreescrever o método) e tem um golpe especial que só ele tem (implementação). Ambos são inimigos mas possuem comportamentos diferentes um do outro
 
Seria estranho se eu não achasse um tutorial seu incrível, sendo que sou seu fã a anos! Parabéns por mais um tutorial super didático! Agora eu entendo, de uma vez por todas, o que é a herança e como aplicar! Você nunca mais vai ver a variável vida várias vezes em vários códigos diferentes nos meus jogos! HAHAHA

Obrigado por no dar mais uma excelente aula! * . *
Meu maravilhoso Joker, mais uma vez obrigada pelo comentário!​
Lembre-se que fiz este tutorial em sua homenagem, já que você me sugeriu ele um tempo atrás, então espero que tenha apreciado de verdade, e que nunca mais espalhe variáveis de "vida" por todo canto! u_u​
Ainda não comecei meus estudos com programação de fato, mas este material com certeza já está aqui na minha lista de coisas pra estudar.
Obrigado por dedicar seu tempo a fazer um conteúdo tão completo e esmiuçado desse jeito pra comunidade.

Isso aí Alke! Inicie seus estudos assim que puder, até lá acredito que teremos mais aulas sobre POO e herança, então dessa forma você vai poder acompanhar tudo na sequência!​
Obrigada pelo apoio!​

Visualizar anexo 2152
Professora @Mayleone, perguntinha de uma aluna leiga aqui...

Quer dizer então que uma classe utilizada como herança pode sofrer alterações para novas criações a partir da "base" que ela carrega?
Em resumo: Objetos com classes semelhantes podem possuir comportamentos distintos? É isso?
Mesmo se eu estiver falando besteira, saiba que gosto muito da forma interativa que explica hahahá!

Olá @Jully Anne fico muito feliz que você esteja acompanhando as aulas por aqui!​
Sobre a sua dúvida, o membro @Guira já fez a gentileza de lhe responder com bastante precisão! Porém, gostaria de dar mais um exemplo sobre herança a partir do Yu-Gi-Oh (sei que vai gostar):​
Vamos supor que eu esteja programando as cartas para o jogo.​
Neste momento eu posso criar uma carta-base que contém informações e ações que todos os tipos de cartas partilham, tais como: Nome, Descrição, e ações de setar e invocar (no caso das spells, ativar).​
Com isto, posso criar minha classe-pai que terão essas informações:​

C #:
public class CardBase {
    public string name, description;
    
    public virtual void SetDown() {
        //Implementação
    }
    
    public virtual void SetUp() {
        //Implementação carta pra cima
    }
}

Agora posso criar cartas de monstros, que além de possuírem nome e descrição, também possuem valor de ataque e defesa e ação de "Flipar", ou seja, as informações da classe-pai + suas próprias informações:​

C #:
public class MonsterCard : CardBase{

public int atk, def;

public void Flip(){
//Implementação
}
}

E também posso criar outro tipo de carta, por exemplo, as cartas de magia: elas não tem ataque nem defesa e nem "flipam", isso é um comportamento exclusivo dos monstros. No caso das magias, elas tem um tipo, como: "rápido, continuado e etc...", uma palavra-chave que chama seu respectivo feitiço, e o comportamento de "SetUp" é diferente dos monstros, pois assim que você joga um feitiço para cima no campo, ele ativa o efeito, portanto, devemos sobrescrever esta ação:​
C #:
public class SpellCard : CardBase {

public string type, keyEffect;

public override void SetUp(){
ActivateEffect();
}

public void ActivateEffect(){
// chamar o efeito através da key
}
}

Agora essa classe possui comportamento da classe-pai, que é ser setada, porém seu comportamento de ser invocada é diferente, pois foi sobrescrito, essa carta chama a ação "ActivateEffect". Essa classe possui também informações próprias além das da classe-pai, tais como: chave de efeito e tipo. Também possui uma ação exclusiva das cartas de magia que é a ativação da mesma, através do método "ActivateEffect".​
Bom, espero que com estes exemplos, o uso da herança e da sobrescrita fiquem mais claros, e que dê para perceber quais as vantagens de se utilizar de tais práticas.​
Qualquer dúvida estou a disposição. o/​



Obrigada por responder a dúvida também! Esse feedback é muito importante e agrega ao meu tópico!​
 
Voltar
Topo Inferior