Nosso banco agora suporta Contas de Investimento. Já sabemos como fazer: basta herdar da classe Conta
:
public class ContaInvestimento : Conta
{
// comportamentos específicos da conta investimento
}
Por lei, uma vez por ano devemos pagar um tributo ao governo relacionado às contas de investimento e contas de poupança. O mesmo não acontece com uma simples Conta Corrente.
Para resolver esse problema, podemos criar um método em ambas as classes que calcula o valor desse tributo. Por exemplo:
public class ContaPoupanca : Conta
{
// outros metodos
public double CalculaTributo() {
return this.Saldo * 0.02;
}
}
public class ContaInvestimento : Conta
{
// outros metodos
public double CalculaTributo() {
return this.Saldo * 0.03;
}
}
Excelente. Os métodos só ficam nas Contas que realmente sofrem esse tributo.
Agora, a próxima funcionalidade é a geração de um relatório, no qual devemos imprimir a quantidade total de tributos pagos por todas as Contas Investimento ou Poupanca do nosso banco. Precisamos de uma classe que acumula o valor de todos os tributos de todas as contas do banco. Esse é um problema parecido com o que já tivemos antes:
public class TotalizadorDeTributos {
public double Total { get; private set; }
public void Acumula(ContaPoupanca cp) {
Total += cp.CalculaTributo();
}
public void Acumula(ContaInvestimento ci) {
Total += ci.CalculaTributo();
}
}
Pronto. Agora basta passarmos ContaInvestimento
ou ContaPoupanca
e nossa classe acumulará o valor do tributo. Repare que toda vez que uma nova conta sofrer um tributo, precisaremos lembrar de voltar na classe TotalizadorDeTributos
e criar um novo método Acumula()
.
Nos capítulos anteriores, resolvemos isso usando polimorfismo. Se a classe pai possuir o método em comum, então basta recebermos uma referência pro tipo pai:
public class TotalizadorDeTributos {
public double Total { get; private set; }
public void Acumula(Conta c) {
Total += c.CalculaTributo();
}
}
Mas será que faz sentido colocar o método CalculaTributo()
na classe Conta
?
public abstract class Conta {
// resto da classe aqui
public abstract double CalculaTributo();
}
Nem todas as Contas são tributáveis. Se fizermos isso, a classe ContaCorrente
ganhará esse método, mas ela não sofre tributo!
Precisamos achar uma maneira de "achar um pai em comum" apenas para a ContaCorrente
e ContaInvestimento
. Classes em C# não podem ter dois pais. Mas o que podemos fazer é dizer para o compilador que garantiremos a existência do método CalculaTributo()
nas classes que chegarem para o método Acumula()
.
Como fazemos isso? Simples. Fazemos a classe "assinar" um contrato! Nesse caso, queremos assinar o contrato que fala que somos Tributáveis. Contratos no C# são conhecidos como interfaces. A declaração de uma interface é praticamente igual a de uma classe, porém utilizamos a palavra interface
ao invés de class
.
public interface Tributavel
{
// código da interface
}
A convenção de nomes do C# para uma interface é seguir a mesma convenção de nomenclatura de classes porém com um I
no começo do nome:
public interface ITributavel
{
}
É uma boa prática colocar o código da interface dentro de um arquivo separado com o mesmo nome da interface. Por exemplo, a interface ITributavel
ficaria no arquivo ITributavel.cs
. Dentro da interface, queremos colocar a declaração do método CalculaTributo()
. Métodos declarados em uma interface nunca possuem implementação e sempre são públicos. A declaração da interface ITributavel
com o método CalculaTributo()
fica da seguinte forma:
// Arquivo ITributavel.cs
public interface ITributavel
{
double CalculaTributo();
}
Queremos fazer com que a conta poupança assine o contrato ITributavel
que acabamos de criar, para isso, precisamos colocar o nome da interface que queremos implementar logo após a declaração da classe pai:
// Arquivo ContaPoupanca.cs
public class ContaPoupanca : Conta, ITributavel
{
// Implementação dos métodos da ContaPoupanca
}
Como a interface ITributavel
declara o método CalculaTributo()
, toda classe que assina a interface é obrigada a dar uma implementação para essa funcionalidade, se não implementarmos o método da interface, a classe não compilará.
public class ContaPoupanca : Conta, ITributavel
{
// resto da classe aqui
// método que sou obrigado a implementar
public double CalculaTributo()
{
return this.Saldo * 0.02;
}
}
Repare que, para implementarmos o método da interface, não podemos utilizar a palavra override
, ela é reservada para a sobrescrita de métodos da Herança. A mesma coisa para a ContaInvestimento
:
public class ContaInvestimento : Conta, ITributavel
{
// resto da classe aqui
// método que sou obrigado a implementar
public double CalculaTributo()
{
return this.Saldo * 0.03;
}
}
Além disso, podemos fazer com que uma classe assine uma interface sem herdar de outra classe. Por exemplo, o banco também trabalha com seguros de vida que também são tributáveis, logo podemos representar essa classe com o seguinte código:
public class SeguroDeVida : ITributavel
{
public double CalculaTributo()
{
// implementação do CalculaTributo
}
}
Dessa forma, podemos dizer que a classe TotalizadorDeTributos
recebe um ITributavel
qualquer. O polimorfismo funciona com interfaces!
public class TotalizadorDeTributos {
public double Total { get; private set; }
public void Acumula(ITributavel t) {
Total += t.CalculaTributo();
}
}
Excelente! Veja que com interfaces conseguimos fazer com que um conjunto de classes implemente os mesmos métodos.
Interfaces são bem mais simples do que classes. Elas não tem atributos e seus métodos não tem implementação. A interface apenas nos garante que o método existirá naquela classe. Por esse motivo, apesar de C# não suportar herança múltipla (ser filho de mais de uma classe), podemos implementar quantas interfaces quisermos. Basta colocar uma na frente da outra:
public class ContaInvestimento : Conta, ITributavel, OutraInterfaceQualquer
{
// implementa os métodos das interfaces Tributavel e OutraInterfaceQualquer
}
Quando uma classe utiliza tanto herança quanto interfaces, precisamos sempre declarar qual é a classe pai e depois as interfaces, assim como fizemos na ContaPoupanca
:
// Repare que primeiro colocamos a classe pai (Conta) e depois as interfaces.
// Se mudarmos a ordem, o código não compilará.
public class ContaPoupanca : Conta, ITributavel
{
// implementação
}
Acostume-se com interfaces. Daqui pra frente, veremos as várias interfaces que existem no C#!
Exercícios
O banco precisa gerenciar os impostos que serão pagos por seus produtos. Para resolver esse problema, criaremos uma nova interface chamada
ITributavel
. Para criar a interface, clique com o botão direito do mouse no nome do projeto e escolha a opçãoAdd > New Item
(o mesmo que utilizamos para criar o formulário de cadastro). Na janela de novo item, escolha a opçãoInterface
e coloque o nomeITributavel
:Faça com que essa interface declare um método chamado
CalculaTributos
que não recebe nenhum argumento e devolve umdouble
que representa o valor do imposto que deve ser pago.O código da interface deve ficar parecido com o seguinte:
public interface ITributavel { double CalculaTributos(); }
O que acontece se tentarmos instanciar uma interface?
ITributavel t = new ITributavel();
Erro de compilação. Interfaces não tem implementação e, logo, não podem ser instanciadas.
O código compila, mas o objeto não faz nada.
O C# busca aleatoriamente uma classe que implementa essa interface e a instancia.
Faça com que a classe
ContaCorrente
implemente a interfaceITributavel
que acabamos de criar, porém ainda não implemente o métodoCalculaTributos
. Tente executar o código. O que aconteceu?Como a
ContaCorrente
assina a interfaceITributavel
, precisamos colocar uma implementação para o métodoCalculaTributos
dentro da classe, se não o código do projeto não compilará. Implemente o métodoCalculaTributos
daContaCorrente
, faça com que aContaCorrente
pague 5% de seu saldo como imposto.Crie uma nova classe no banco chamada
SeguroDeVida
e faça com que essa classe implemente a interfaceITributavel
. O métodoCalculaTributos
doSeguroDeVida
deve devolver um valor constante de 42 reais.Agora vamos adicionar um novo botão no formulário que calculará os impostos do banco. Chame-o de
botaoImpostos
. No código desse botão, teste o métodoCalculaTributos
em diferentes situações, por exemplo:private void botaoImpostos_Click(object sender, EventArgs e) { ContaCorrente conta = new ContaCorrente(); conta.Deposita(200.0); MessageBox.Show("imposto da conta corrente = " + conta.CalculaTributos()); ITributavel t = conta; MessageBox.Show("imposto da conta pela interface = " + t.CalculaTributos()); SeguroDeVida sv = new SeguroDeVida(); MessageBox.Show("imposto do seguro = " + sv.CalculaTributos()); t = sv; MessageBox.Show("imposto do seguro pela interface" + t.CalculaTributos()); }
Depois de implementar seus testes, tente clicar no botão para ver o que acontece.
(Opcional) Crie uma nova classe chamada
TotalizadorDeTributos
, que será responsável por acumular os impostos de diferentes produtos tributáveis do banco:public class TotalizadorDeTributos { public double Total { get; private set; } public void Adiciona(ITributavel t) { this.Total += t.CalculaTributos(); } }
Depois de criar essa classe, modifique o código do botão do exercício passado para que ele utilize a classe que acabamos de criar para calcular o total de impostos. Por exemplo:
private void botaoImpostos_Click(object sender, EventArgs e) { ContaCorrente conta = new ContaCorrente(); conta.Deposita(200.0); SeguroDeVida sv = new SeguroDeVida(); TotalizadorDeTributos totalizador = new TotalizadorDeTributos(); totalizador.Adiciona(conta); MessageBox.Show("Total: " + totalizador.Total); totalizador.Adiciona(sv); MessageBox.Show("Total: " + totalizador.Total); }
(Desafio) Pesquise sobre a palavra is do C# no seguinte link http://msdn.microsoft.com/en-us/library/scekt9xw.aspx e depois tente modificar o código o botão para que ele seja capaz de calcular automaticamente o imposto de todas as contas correntes que estão cadastradas no array de contas da aplicação.