Em nosso banco os clientes podem ter dois tipos de conta até o momento: conta corrente ou conta poupança. Para instanciar estes tipos de conta, poderíamos usar o seguinte código:
//Instanciar uma nova conta corrente
ContaCorrente contaCorrente = new ContaCorrente();
//Instancia uma nova conta poupança
ContaPoupanca contaPoupanca = new ContaPoupanca();Nos capítulos anteriores, aprendemos que essas duas classes têm muito em comum, ambas são contas. Não apenas têm atributos em comum, mas também comportamentos. Para evitar a repetição do código em ambas as classes, vimos como isolar este código repetido em uma classe Conta, e fazer com que as classes ContaCorrente e ContaPoupanca herdem dessa classe mãe todo o código em comum.
Além da reutilização de código, também vimos a possibilidade de escrever métodos que podem receber argumentos tanto do tipo ContaCorrente quanto do tipo ContaPoupanca, utilizando polimorfismo. Basta fazermos os métodos referenciarem o tipo mais genérico, no caso, Conta:
public class TotalizadorDeContas
{
// ...
public void Soma(Conta conta)
{
// ...
}
}Mas o que acontece quando executamos a seguinte linha:
Conta c = new Conta();Criamos um novo objeto do tipo Conta. Mas esse objeto faz algum sentido para nossas regras de negócio? É uma conta genérica, não sendo nem conta corrente e nem poupança.
Neste caso, não deveríamos permitir a instanciação de objetos Conta.
Conta é apenas uma ideia em nosso domínio, uma forma genérica de referenciarmos os dois tipos de conta que realmente existem em nosso sistema, ContaCorrente e ContaPoupanca. Podemos evitar a criação de objetos do tipo Conta definindo a classe como abstrata:
public abstract class Conta
{
// ...
}Desta forma, não podemos mais criar objetos do tipo Conta, mas podemos ainda usar variáveis do tipo conta, para referenciar objetos de outros tipos:
Conta conta = new Conta(); //não compila, não pode criar objetos abstratos
Conta cc = new ContaCorrente(); // pode, objeto é do tipo ContaCorrente
Conta cp = new ContaPoupanca(); // pode, objeto é do tipo ContaPoupancaRepare que o calculo necessário para realizar um saque é diferente em cada um dos tipos de Conta. Sabemos que uma conta deve ter um método Saca, mas a implementação deste método depende de regras específicas de cada tipo diferente de conta em nosso sistema. Uma solução possível seria implementá-lo sem fazer nada, mas dizendo que ele pode ser sobrescrito (virtual):
public abstract class Conta
{
public virtual void Saca(double valor){
//não faz nada
}
// ...
}E manter o código Saca original nas classes filhas, dizendo que eles sobrescrevem (override) o método na classe pai:
public class ContaCorrente : Conta
{
public override void Saca(double valor)
{
this.Saldo -= (valor + 0.10);
}
// ...
}
public class ContaPoupanca : Conta
{
public override void Saca(double valor)
{
this.Saldo -= valor;
}
// ...
}Desejamos que toda classe filha implemente sua própria versão do método, com o comportamento referente ao tipo da conta. Mas se esquecermos de sobrescrever o método Saca em uma subclasse, o método herdado da classe Conta será executado, que não faz nada. Não queremos isso! Queremos obrigar as classes filha a implementar o método Saca.
Podemos obrigar todas as classes filhas a sobrescreverem um método na classe mãe, basta declarar esse método com o modificador abstract ao invés de virtual. Toda vez que marcamos um método com o modificador abstract, ele obrigatoriamente não pode ter uma implementação padrão:
public abstract class Conta // marcando que a classe está incompleta
{
public abstract void Saca(double valor); // marcando que o método está incompleto
}Com essa modificação, o método Saca passa a representar apenas uma ideia, que precisa de uma implementação concreta nas classes filhas. Caso não implementemos esse método na classe filha, o compilador emitirá um erro, avisando da obrigatoriedade de sobrescrever este método. Então se implementássemos, por exemplo, a classe ContaPoupanca sem definir a implementação do Saca, o código da classe não compilará:
public class ContaPoupanca : Conta
{
// Essa classe não compila pois não colocamos a implementação para o Saca
}Podemos ter uma classe abstrata sem nenhum método abstrato, mas não o contrário. Se a classe tem pelo menos um método abstrato, ela deve ser abstrata também pois como o método está incompleto, a classe não está completa.
Conta em uma classe abstrata. Repare que agora teremos um erro de compilação em todos os pontos do código em que tentamos instanciar o tipo Conta. Por quê? Modifique o código da sua aplicação para que a conta não seja instanciada, assim corrigiremos os erros de compilação. Não se esqueça de sempre testar o seu código.Saca e Deposita da classe Conta, porém cada tipo de Conta sobrescreve esses métodos, logo eles são bons candidatos para métodos abstratos. Transforme os métodos Saca e Deposita em métodos abstratos, repare que com isso todas as classes filhas são obrigadas a dar uma implementação para esses métodos.