top of page
  • Foto do escritorFábio Henrique

Do zero a iniciante - C# herança, interface, classe abstrata

Herança, interface e classe abstrata são conceitos importantes na programação orientada à objeto.


Quando projetamos classes é comum que algumas delas possuam características semelhantes. Toda vez que identificar algo neste sentido é hora de pensar em usar algum dos conceitos mencionados acima.


Herança


As classes abaixo representam um homem e uma mulher


class Homem
{
     public int QtdBracos { getset; }
     public int QtdPernas { getset; }
     public int QtdNariz { getset; }
     public string Nome { getset; }

     public void Andar()
     {
     }

     public void Comer()
     {
     }
}

 class Mulher
 {
   public int QtdBracos { getset; }
   public int QtdPernas { getset; }
   public int QtdNariz { getset; }
   public string Nome { getset; }

   public void Andar()
   {
   }

   public void Comer()
   {
   }
}

Repare que as classes têm exatamente os mesmo métodos e propriedades. Podemos utilizar a herança para não termos essa duplicidade de código. Pense o seguinte, homem e mulher são formas específicas de se classificar seres humanos, logo podemos criar uma classe chamada Humano da qual Homem e Mulher irão herdar propriedades e métodos.


class Humano
{
      public int QtdBracos { getset; }
      public int QtdPernas { getset; }
      public int QtdNariz { getset; }
      public string Nome { getset; }
 
      public void Andar()
      {
      }

      public void Comer()
      {
      }
}

 class Homem : Humano
 {
 }

 class Mulher Humano
 {
 }

Agora não temos mais a duplicidade de código e às classes Mulher e Homem continuam tendo os mesmo métodos e propriedades que antes.


As classes Mulher e Homem são subclasses da classe Humano.


Em relação às classes Mulher e Homem, a classe Humano pode ser chamada de: super classe, classe pai ou classe base.


Toda subclasse estende sua classe base. Isso significa que uma classe não está limitada a sua classe pai. Pense o seguinte, embora homem e mulher sejam seres humanos apenas a mulher é capaz de ter uma gestação, logo não faria sentido criar um método gestação na classe Humano, pois isso significaria que a classe Homem também teria acesso à este método, o que não faz sentido algum já que um homem não é capaz disso. Então o correto seria criar o método gestação diretamente na classe Mulher.


class Humano
{
      public int QtdBracos { getset; }
      public int QtdPernas { getset; }
      public int QtdNariz { getset; }
      public string Nome { getset; }
 
      public void Andar()
      {
      }

      public void Comer()
      {
      }
}

 class Homem : Humano
 {
 }

 class Mulher Humano
 {
      public void Gestacao()
      {
      }
 }

Uma subclasse pode sobrescrever métodos herdados. No C# isto é feito através das palavras reservadas virtual e override.


Por padrão humanos são onívoros, comem de tudo, porém algumas pessoas optam por hábitos alimentares diferentes tais como vegetarianismo e veganismo. Este é um caso perfeito para sobrescrita de método (comportamento).


class Humano
{
      public int QtdBracos { getset; }
      public int QtdPernas { getset; }
      public int QtdNariz { getset; }
      public string Nome { getset; }
 
      public void Andar()
      {
      }

      virtual public void Comer()
      {
         Console.WriteLine("Como qualquer coisa.");
      }
}

 class Homem : Humano
 {
 }

 class Mulher Humano
 {
      public void Gestacao()
      {
      }
 }

class Raquel : Mulher
{
      override public void Comer()
      {
         Console.WriteLine("Como apenas vegetais.");
      }
}

Na classe base, marque os métodos que deseja permitir sobrescrita com a palavra reservada virtual. Na subclasse marque os métodos que deseja sobrescrever com a palavra reservada override.


Em qualquer lugar onde se pode usar uma classe base, pode-se utilizar uma de suas subclasses.


Uma das coisas mais úteis que você pode fazer com herança é usar uma subclasse no lugar da classe base da qual ela herda. No exemplo abaixo, adicionei o método Conversar à classe Humano. Este método recebe como parâmetro o humano, o qual irá interagir. Isto significa que podemos passar, como parâmetro, para este método uma instância de qualquer uma das seguintes classes: Homem, Mulher, Raquel e até mesmo uma instância da classe Humano.


class Humano
{
      public int QtdBracos { getset; }
      public int QtdPernas { getset; }
      public int QtdNariz { getset; }
      public string Nome { getset; }
 
      public void Andar()
      {
      }

      virtual public void Comer()
      {
         Console.WriteLine("Como qualquer coisa.");
      }

      public void Conversar(Humano humano)
      {
         Console.WriteLine($"Oi {humano.Nome}, como você está?");
      }
}

Exemplo de uso


Homem homem = new Homem();

Mulher mulher = new Mulher();
mulher.Conversar(homem);

A saída do código de acima será: "Oi , como você está?". O erro ai é devido a ausência de valor na propriedade Nome. Vamos tornar esta propriedade obrigatória através da criação de um construtor na classe Humano.


class Humano
{
      public Humano(string nome)
      {
         Nome = nome;
      }

      public int QtdBracos { getset; }
      public int QtdPernas { getset; }
      public int QtdNariz { getset; }
      public string Nome { getset; }
 
      public void Andar()
      {
      }

      virtual public void Comer()
      {
         Console.WriteLine("Como qualquer coisa.");
      }

      public void Conversar(Humano humano)
      {
         Console.WriteLine($"Oi {humano.Nome}, como você está?");
      }
}

Ao exigir um parâmetro no construtor da classe base todas as subclasses devem implementar um construtor que inicializa o construtor de sua classe base. A palavra reservada base permite acessar diretamente métodos e propriedades da classe pai


class Homem : Humano
{
        public Homem(string nome) : base(nome)
        {
        }
}

 class Mulher Humano
 {
        public Mulher(string nome) : base(nome)
        {
        }

         public void Gestacao()
         {
         }
 }

class Raquel : Mulher
{
        public Raquel(string nome) : base(nome)
        {
        }

        override public void Comer()
        {
           Console.WriteLine("Como apenas vegetais.");
        }
}

Exemplo de uso


Homem homem = new Homem("Fábio");

Mulher mulher = new Mulher("Raquel");
mulher.Conversar(homem);

A saída do código de acima será: "Oi Fábio, como você está?" Este é o resultado esperado.


Interface


Embora seres humanos compartilhem características semelhantes, as decisões tomadas ao longo da vida faz com que eles adquiram habilidades e especializações distintas.


Vamos considerar duas pessoas, Fábio e Annabelle, bem como suas habilidades específicas


Fábio

  • Conversar em português e inglês

Annabelle

  • Conversar em inglês e francês


Vamos pensar agora como representar isso no código. No momento, fica claro que:

  • Fábio e Annabelle são humanos

  • Fábio é homem e Annabelle é mulher


Num primeiro momento você poderia pensar: "Ah isso é simples, basta criar uma classe Fabio que herda da classe Homem e uma classe Annabelle que herda da classe Mulher e implementar direto nessas classes suas respectivas características".


class Annabelle : Mulher
{
        public Annabelle(string nome) : base(nome)
        {
        }

        public void ConversarEmIngles(Humano humano)
        {
           Console.WriteLine($"Hey {humano.Nome}, how are you?");
        }

        public void ConversarEmFrances(Humano humano)
        {
           Console.WriteLine($"Salut {humano.Nome}, comment ça va?");
        }
}

class Fabio : Homem
{
        public Fabio(string nome) : base(nome)
        {
        }

        public void ConversarEmPortugues(Humano humano)
        {
           Console.WriteLine($"Oi {humano.Nome}, como você está?");
        }

        public void ConversarEmIngles(Humano humano)
        {
           Console.WriteLine($"Hey {humano.Nome}, how are you?");
        }
}

O código acima é funcional, mas os métodos de conversar assumem que todo humano sabe conversar em todas as línguas, o que não é verdade. Outra coisa que não faz sentido é ter o método Conversar na classe Humano. Podemos concluir então que a abordagem acima é meio bosta né.



É neste tipo de situação que interfaces caem como uma luva! Uma Interface diz para uma classe que ela precisa implementar certos métodos e propriedades. Para o exemplo acima irei criar 3 interfaces uma para cada um dos idiomas acima (Português, Inglês, Francês)


interface IFalantePortugues
{
      void Conversar(IFalantePortugues pessoa);
}

 interface IFalanteIngles
 {
      void Conversar(IFalanteIngles pessoa);
 }

 interface IFalanteFrances
 {
      void Conversar(IFalanteFrances pessoa);
 }

A declaração de uma interface é feita através da palavra reservada interface. Por convenção os nomes de interfaces começam com I maiúsculo, não há uma regra que o obrigue a isto, mas fazê-lo torna seu código mais fácil de entender. Você pode ver por si mesmo o quanto. Entre no IDE em qualquer linha em branco dentro de qualquer método e digite "I" - O IntelliSense mostrará as interfaces .NET


Adicionar uma interface ao seu programa é muito parecido com acrescentar uma classe, exceto que você nunca escreve nenhum método. Você apenas define o tipo de retorno e parâmetros dos métodos, mas em vez de um bloco de instruções dentro de chaves, você termina a linha com um ponto e vírgula.


Tudo numa interface pública é automaticamente público porque você irá usá-la para definir os métodos e as propriedades públicas de qualquer classe que a implemente.


class Fabio : HomemIFalantePortuguesIFalanteIngles
{
     public Fabio(string nome) : base(nome)
     {
     }

     public void Conversar(IFalantePortugues pessoa)
     {
         Console.WriteLine($"Oi, como você está?");
     }

     public void Conversar(IFalanteIngles pessoa)
     {
         Console.WriteLine($"Hey, how are you?");
     }
}

class Annabelle : MulherIFalanteInglesIFalanteFrances
{
     public Annabelle(string nome) : base(nome)
     {
     }

     public void Conversar(IFalanteFrances pessoa)
     {
         Console.WriteLine($"Salut, comment ça va?");
     }

     public void Conversar(IFalanteIngles pessoa)
     {
        Console.WriteLine($"Hey, how are you?");
     }
}

class Raquel : MulherIFalantePortugues
{
      public Raquel(string nome) : base(nome)
      {
      }
 
      override public void Comer()
      {
         Console.WriteLine("Como apenas vegetais.");
      }

      public void Conversar(IFalantePortugues pessoa)
      {
         Console.WriteLine($"Oi , como você está?");
      }
}

O legal da interface é que você pode implementar várias delas. Ao contrário de uma classe que é possível herdar apenas de uma. Atente-se a nomeclatura, uma interface você implementa e uma classe você herda.


O código abaixo produzirá o seguinte output:

  1. Hey, how are you?

  2. Oi, como você está?

 Fabio fabio = new Fabio("Fábio");
 Annabelle annabelle = new Annabelle("Annabelle");
 Raquel raquel = new Raquel("Raquel");

 fabio.Conversar(annabelle);
 fabio.Conversar(raquel);

O método Conversar da classe Fabio produz resultados diferentes de acordo com o que é passado como parâmetro.


Vários métodos podem ter o mesmo nome com parâmetros diferentes isto é o que chamamos de overload (sobrecarga).


Após implementar as interfaces perdemos o acesso a propriedade Nome da classe Humano. Podemos resolver isso utilizando os operadores is e as


is diz a você o que um objeto implementa ou herda, as diz ao compilador como tratar seu objeto.


Veja o código abaixo para entender o uso do is e do as


class Fabio : HomemIFalantePortuguesIFalanteIngles
{
     public Fabio(string nome) : base(nome)
     {
     }

     public void Conversar(IFalantePortugues pessoa)
     {
         if (pessoa is Humano)
         {
            var humano = pessoa as Humano;
            Console.WriteLine($"Oi {humano.Nome}, como você está?");
         }
     }

     public void Conversar(IFalanteIngles pessoa)
     {
           if (pessoa is Humano)
           {
                var humano = pessoa as Humano;
                Console.WriteLine($"Hey {humano.Nome}, how are you?");
           }
     }
}

class Annabelle : MulherIFalanteInglesIFalanteFrances
{
     public Annabelle(string nome) : base(nome)
     {
     }

     public void Conversar(IFalanteFrances pessoa)
     {
         if (pessoa is Humano)
         {
            var humano = pessoa as Humano;
            Console.WriteLine($"Salut {humano.Nome}, comment ça va?");
         }
     }

     public void Conversar(IFalanteIngles pessoa)
     {
           if (pessoa is Humano)
           {
                var humano = pessoa as Humano;
                Console.WriteLine($"Hey {humano.Nome}, how are you?");
           }
     }
}

Agora o código abaixo produzirá o seguinte output:

  1. Hey Annabelle, how are you?

  2. Oi Raquel, como você está?

 Fabio fabio = new Fabio("Fábio");
 Annabelle annabelle = new Annabelle("Annabelle");
 Raquel raquel = new Raquel("Raquel");

 fabio.Conversar(annabelle);
 fabio.Conversar(raquel);

A classe Fabio pode conversar com a classe Annabelle pois ambos implementam a interface IFalanteIngles. E se a classe Fabio passar a implementar a interface IFalanteFrances o que aconteceria com a instrução abaixo?


fabio.Conversar(annabelle);

A resposta é o compilador apontaria o seguinte erro:



Podemos resolver este erro de duas formas bem simples.


Criando uma referência da interface, a qual você deseja usar. A saída do código abaixo será: "Salut Annabelle, comment ça va?"

 IFalanteFrances falanteFrances = annabelle;
 fabio.Conversar(falanteFrances);

Informando ao compilador como o objeto deve ser tratado. A saída do código abaixo será: "Hey Annabelle, how are you?"

 fabio.Conversar(annabelle as IFalanteIngles);

Classe Abstrata


Uma classe abstrata é como um cruzamento entre uma classe e uma interface.

  • Uma classe abstrata não pode ser instanciada.

  • Uma classe abstrata pode conter métodos concretos e abstratos. Métodos concretos são possuem implementação já os métodos abstratos possuem apenas assinatura, assim como nas interfaces!

  • A classe que herdar de uma classe abstrata obrigatoriamente deverá implementar seus métodos abstratos.

  • Uma classe abstrata é definida através da palavra reservada abstract.


No exemplo deste post a classe Humano é um ótimo candidato a se tornar uma classe abstrata. Isso se deve ao fato de que além de fornecer algumas implementações básicas para todos os humanos algumas coisas devem ficar a cargo de quem irá herdar desta classe. Por exemplo, quando se trata da produção de hormônios um homem produz testosterona e uma mulher estrógeno. Logo, uma classe abstrata pode definir que quem herde dela implemente sua própria produção de hormônio.


Vamos ver isso na prática!


abstract class Humano
{
     public Humano(string nome)
     {
        Nome = nome;
     }

      public int QtdBracos { getset; }
      public int QtdPernas { getset; }
      public int QtdNariz { getset; }
      public string Nome { getset; }

      public void Andar()
      {
         Console.WriteLine("Andando sobre duas pernas.");
      }

      virtual public void Comer()
      {
         Console.WriteLine("Como qualquer coisa.");
      }

      abstract public void ProduzirHormonio();
}

No código acima, o método ProduzirHormonio foi definido como abstract o que significa que toda classe que herdar de Humano deverá ter sua própria implementação do mesmo.


class Homem : Humano
{
     public Homem(string nome) : base(nome)
     {
     }

      public override void ProduzirHormonio()
      {
         Console.WriteLine("Testosterona");
      }
}

 class Mulher : Humano
 {
      public Mulher(string nome) : base(nome)
      {
      }

       public void Gestacao()
       {
       }

       public override void ProduzirHormonio()
       {
          Console.WriteLine("Estrógeno");
       }
}

No código acima podemos observar que tanto homem quanto mulher implementaram sua própria versão do método ProduzirHormonio. Quando você herda de uma classe abstrata, precisa sobrescrever todos os seus métodos abstratos e isto é feito através da palavra reservada override.


 

Observações


Se você seguiu todos os posts da série "Do zero a iniciante" até aqui, meus parabéns! Com este post você acabou de concluir o aprendizado sobre os 4 princípios da programação orientada a objetos.

  • Herança - Isto apenas quer dizer ter uma classe ou interface que herda de outra

  • Abstração - Você está usando abstração quando cria um modelo de classe que começa com classes mais gerais - ou abstratas - e tem classes mais específicas que herdam dela.

  • Encapsulamento - Quer dizer criar um objeto que controla seu estado internamente usando campos privados e usar propriedades e métodos públicos para permitir que outras classes trabalhem apenas a parte dos dados internos necessários.

  • Polimorfismo - Você está usando polimorfismo quando pega uma instância de uma classe e a usa em uma instrução ou um método que espera um tipo diferente, tal como uma classe base ou uma interface que uma classe implementa.


Não desanime só porque achou difícil, isto é coisa de gente medíocre. Mesmo programadores experientes têm dificuldades em aplicar estes conceitos e projetar boas hierarquias de classes.


Nada pode ser obtido sem sacrifício. Para se obter algo é preciso oferecer algo em troca de igual valor.



 

Deixe seus elogios, críticas e dúvidas nos comentários!

431 visualizações0 comentário

Posts recentes

Ver tudo

Opmerkingen


bottom of page