Você está na página 1de 11

Programação orientada a objeto (C#)

Artigo • 08/06/2023

O C# é uma linguagem de programação orientada a objeto. Os quatro princípios


básicos da programação orientada a objetos são:

Abstração Modelando os atributos e interações relevantes de entidades como


classes para definir uma representação abstrata de um sistema.
Encapsulamento Ocultando o estado interno e a funcionalidade de um objeto e
permitindo apenas o acesso por meio de um conjunto público de funções.
Herança Capacidade de criar novas abstrações com base em abstrações existentes.
Polimorfismo Capacidade de implementar propriedades ou métodos herdados de
diferentes maneiras em várias abstrações.

No tutorial anterior, introdução às classes que você viu abstração e encapsulamento. A


classe BankAccount forneceu uma abstração para o conceito de conta bancária. Você
pode modificar sua implementação sem afetar nenhum dos códigos que usaram a
classe BankAccount . As classes BankAccount e Transaction classes fornecem
encapsulamento dos componentes necessários para descrever esses conceitos no
código.

Neste tutorial, você estenderá esse aplicativo para usar herança e polimorfismo para
adicionar novos recursos. Você também adicionará recursos à BankAccount classe,
aproveitando as técnicas de abstração e encapsulamento que aprendeu no tutorial
anterior.

Criar diferentes tipos de contas


Depois de criar este programa, você recebe solicitações para adicionar recursos a ele.
Funciona muito bem na situação em que há apenas um tipo de conta bancária. Ao
longo do tempo, as necessidades são alteradas e os tipos de conta relacionados são
solicitados:

Uma conta de ganho de juros que acumula juros no final de cada mês.
Uma linha de crédito que pode ter saldo negativo, mas quando há saldo, há
cobrança de juros a cada mês.
Uma conta de cartão presente pré-paga que começa com um único depósito, e só
pode ser paga. Ela pode ser preenchida novamente uma vez no início de cada mês.

Todas essas contas diferentes são semelhantes à classe BankAccount definida no


tutorial anterior. Você pode copiar esse código, renomear as classes e fazer
modificações. Essa técnica funcionaria a curto prazo, mas seria mais trabalho ao longo
do tempo. Todas as alterações seriam copiadas em todas as classes afetadas.
Em vez disso, você pode criar novos tipos de conta bancária que herdam métodos e
dados da classe BankAccount criada no tutorial anterior. Essas novas classes podem
estender a classe BankAccount com o comportamento específico necessário para
cada tipo:

C#

public class InterestEarningAccount : BankAccount


{
}

public class LineOfCreditAccount : BankAccount


{
}

public class GiftCardAccount : BankAccount


{
}

Cada uma dessas classes herda o comportamento compartilhado de sua classe base
compartilhada, a classe BankAccount . Escreva as implementações para
funcionalidades novas e diferentes em cada uma das classes derivadas. Essas classes
derivadas já têm todo o comportamento definido na classe BankAccount .

É uma boa prática criar cada nova classe em um arquivo de origem diferente. No
Visual Studio , você pode clicar com o botão direito do mouse no projeto e
selecionar adicionar classe para adicionar uma nova classe em um novo arquivo. No
Visual Studio Code , selecione Arquivo e Novo para criar um novo arquivo de origem.
Em qualquer ferramenta, nomeie o arquivo para corresponder à classe:
InterestEarningAccount.cs, LineOfCreditAccount.cs e GiftCardAccount.cs.

Ao criar as classes, conforme mostrado no exemplo anterior, você descobrirá que


nenhuma das suas classes derivadas é compilada. Um construtor é responsável por
inicializar um objeto. Um construtor de classe derivada deve inicializar a classe
derivada e fornecer instruções sobre como inicializar o objeto de classe base incluído
na classe derivada. A inicialização adequada normalmente ocorre sem nenhum código
extra. A classe BankAccount declara um construtor público com a seguinte assinatura:

C#

public BankAccount(string name, decimal initialBalance)


O compilador não gera um construtor padrão quando você define um construtor por
conta própria. Isso significa que cada classe derivada deve chamar explicitamente esse
construtor. Você declara um construtor que pode passar argumentos para o
construtor de classe base. O código a seguir mostra o construtor para o
InterestEarningAccount :

C#

public InterestEarningAccount(string name, decimal initialBalance) :


base(name, initialBalance)
{
}

Os parâmetros para esse novo construtor correspondem ao tipo de parâmetro e aos


nomes do construtor de classe base. Use a sintaxe : base() para indicar uma
chamada para um construtor de classe base. Algumas classes definem vários
construtores e essa sintaxe permite que você escolha qual construtor de classe base
você chama. Depois de atualizar os construtores, você pode desenvolver o código
para cada uma das classes derivadas. Os requisitos para as novas classes podem ser
declarados da seguinte maneira:

Uma conta de ganho de juros:


Obterá um crédito de 2% do saldo final do mês.
Uma linha de crédito:
Pode ter um saldo negativo, mas não ser maior em valor absoluto do que o
limite de crédito.
Incorrerá em uma cobrança de juros a cada mês em que o saldo de fim de mês
não seja 0.
Incorrerá em uma taxa em cada saque que ultrapassa o limite de crédito.
Uma conta de cartão presente:
Pode ser preenchido novamente com um valor especificado uma vez por mês,
no último dia do mês.

Você pode ver que todos os três tipos de conta têm uma ação que ocorre no final de
cada mês. No entanto, cada tipo de conta faz tarefas diferentes. Você usa polimorfismo
para implementar esse código. Crie um único método virtual na classe
BankAccount :

C#

public virtual void PerformMonthEndTransactions() { }


O código anterior mostra como você usa a palavra-chave virtual para declarar um
método na classe base para o qual uma classe derivada pode fornecer uma
implementação diferente. Um método virtual é um método em que qualquer classe
derivada pode optar por reimplementar. As classes derivadas usam a palavra-chave
override para definir a nova implementação. Normalmente, você se refere a isso
como "substituindo a implementação da classe base". A palavra-chave virtual
especifica que as classes derivadas podem substituir o comportamento. Você também
pode declarar métodos abstract em que classes derivadas devem substituir o
comportamento. A classe base não fornece uma implementação para um método
abstract . Em seguida, você precisa definir a implementação para duas das novas
classes que você criou. Inicie com um InterestEarningAccount :

C#

public override void PerformMonthEndTransactions()


{
if (Balance > 500m)
{
decimal interest = Balance * 0.02m;
MakeDeposit(interest, DateTime.Now, "apply monthly inte-
rest");
}
}

Adicione o seguinte código ao LineOfCreditAccount . O código nega o saldo para


calcular uma taxa de juros positiva que é retirada da conta:

C#

public override void PerformMonthEndTransactions()


{
if (Balance < 0)
{
// Negate the balance to get a positive interest charge:
decimal interest = -Balance * 0.07m;
MakeWithdrawal(interest, DateTime.Now, "Charge monthly inte-
rest");
}
}

A classe GiftCardAccount precisa de duas alterações para implementar sua


funcionalidade de fim de mês. Primeiro, modifique o construtor para incluir um valor
opcional a ser adicionado a cada mês:

C#
private readonly decimal _monthlyDeposit = 0m;

public GiftCardAccount(string name, decimal initialBalance, decimal


monthlyDeposit = 0) : base(name, initialBalance)
=> _monthlyDeposit = monthlyDeposit;

O construtor fornece um valor padrão para o valor monthlyDeposit para que os


chamadores possam omitir um 0 sem depósito mensal. Em seguida, substitua o
método PerformMonthEndTransactions para adicionar o depósito mensal, se ele foi
definido como um valor diferente de zero no construtor:

C#

public override void PerformMonthEndTransactions()


{
if (_monthlyDeposit != 0)
{
MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly depo-
sit");
}
}

A substituição aplica o conjunto de depósito mensal no construtor. Adicione o


seguinte código ao método Main para testar essas alterações para GiftCardAccount
e InterestEarningAccount :

C#

var giftCard = new GiftCardAccount("gift card", 100, 50);


giftCard.MakeWithdrawal(20, DateTime.Now, "get expensive coffee");
giftCard.MakeWithdrawal(50, DateTime.Now, "buy groceries");
giftCard.PerformMonthEndTransactions();
// can make additional deposits:
giftCard.MakeDeposit(27.50m, DateTime.Now, "add some additional spen-
ding money");
Console.WriteLine(giftCard.GetAccountHistory());

var savings = new InterestEarningAccount("savings account", 10000);


savings.MakeDeposit(750, DateTime.Now, "save some money");
savings.MakeDeposit(1250, DateTime.Now, "Add more savings");
savings.MakeWithdrawal(250, DateTime.Now, "Needed to pay monthly
bills");
savings.PerformMonthEndTransactions();
Console.WriteLine(savings.GetAccountHistory());

Verifique os resultados. Agora, adicione um conjunto semelhante de código de teste


para LineOfCreditAccount :
C#

var lineOfCredit = new LineOfCreditAccount("line of credit", 0);


// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly
advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for
repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on
repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

Ao adicionar o código anterior e executar o programa, você verá algo semelhante ao


seguinte erro:

Console

Unhandled exception. System.ArgumentOutOfRangeException: Amount of


deposit must be positive (Parameter 'amount')
at OOProgramming.BankAccount.MakeDeposit(Decimal amount, DateTime
date, String note) in BankAccount.cs:line 42
at OOProgramming.BankAccount..ctor(String name, Decimal initialBa-
lance) in BankAccount.cs:line 31
at OOProgramming.LineOfCreditAccount..ctor(String name, Decimal
initialBalance) in LineOfCreditAccount.cs:line 9
at OOProgramming.Program.Main(String[] args) in Program.cs:line 29

7 Observação

A saída real inclui o caminho completo para a pasta com o projeto. Os nomes das
pastas foram omitidos para brevidade. Além disso, dependendo do formato de
código, os números de linha podem ser ligeiramente diferentes.

Esse código falha porque BankAccount pressupõe que o saldo inicial deve ser maior
que 0. Outra suposição feita na classe BankAccount é que o saldo não pode ficar
negativo. Em vez disso, qualquer retirada que sobrecarrega a conta é rejeitada. Ambas
as suposições precisam mudar. A linha de conta de crédito começa em 0 e geralmente
terá um saldo negativo. Além disso, se um cliente empresta muito dinheiro, ele incorre
em uma taxa. A transação é aceita, só custa mais. A primeira regra pode ser
implementada adicionando um argumento opcional ao construtor BankAccount que
especifica o saldo mínimo. O padrão é 0 . A segunda regra requer um mecanismo que
permite que classes derivadas modifiquem o algoritmo padrão. De certa forma, a
classe base "pergunta" ao tipo derivado o que deve acontecer quando há um cheque
especial. O comportamento padrão é rejeitar a transação lançando uma exceção.
Vamos começar adicionando um segundo construtor que inclui um parâmetro
minimumBalance opcional. Esse novo construtor faz todas as ações feitas pelo
construtor existente. Além disso, ele define a propriedade de saldo mínimo. Você pode
copiar o corpo do construtor existente, mas isso significa dois locais a serem alterados
no futuro. Em vez disso, você pode usar o encadeamento de construtor para que um
construtor chame outro. O código a seguir mostra os dois construtores e o novo
campo adicional:

C#

private readonly decimal _minimumBalance;

public BankAccount(string name, decimal initialBalance) : this(name,


initialBalance, 0) { }

public BankAccount(string name, decimal initialBalance, decimal mini-


mumBalance)
{
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

Owner = name;
_minimumBalance = minimumBalance;
if (initialBalance > 0)
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

O código anterior mostra duas novas técnicas. Primeiro, o campo minimumBalance é


marcado como readonly . Isso significa que o valor não pode ser alterado depois que
o objeto é construído. Depois que BankAccount é criado, minimumBalance não pode
alterar. Em segundo lugar, o construtor que usa dois parâmetros : this(name,
initialBalance, 0) { } como implementação. A expressão : this() chama o
outro construtor, aquele com três parâmetros. Essa técnica permite que você tenha
uma única implementação para inicializar um objeto, embora o código do cliente
possa escolher um dos muitos construtores.

Essa implementação chamará MakeDeposit somente se o saldo inicial for maior que
0 . Isso preserva a regra de que os depósitos devem ser positivos, mas permite que a
conta de crédito abra com um saldo 0 .

Agora que a classe BankAccount tem um campo somente leitura para o saldo mínimo,
a alteração final é alterar o código físico 0 para minimumBalance no método
MakeWithdrawal :

C#

if (Balance - amount < _minimumBalance)

Depois de estender a classe BankAccount , você pode modificar o construtor


LineOfCreditAccount para chamar o novo construtor base, conforme mostrado no
seguinte código:

C#

public LineOfCreditAccount(string name, decimal initialBalance, deci-


mal creditLimit) : base(name, initialBalance, -creditLimit)
{
}

Observe que o construtor LineOfCreditAccount altera o sinal do parâmetro


creditLimit para que corresponda ao significado do parâmetro minimumBalance .

Regras de cheque especial diferentes


O último recurso a ser adicionado permite que LineOfCreditAccount cobre uma taxa
por ultrapassar o limite de crédito em vez de recusar a transação.
Uma técnica é definir uma função virtual na qual você implementa o comportamento
necessário. A classe BankAccount refatora o método MakeWithdrawal em dois
métodos. O novo método faz a ação especificada quando o saque leva o saldo abaixo
do mínimo. O método existente MakeWithdrawal tem o seguinte código:

C#

public void MakeWithdrawal(decimal amount, DateTime date, string


note)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount
of withdrawal must be positive");
}
if (Balance - amount < _minimumBalance)
{
throw new InvalidOperationException("Not sufficient funds for
this withdrawal");
}
var withdrawal = new Transaction(-amount, date, note);
_allTransactions.Add(withdrawal);
}

Substitua-o pelo seguinte código:

C#

public void MakeWithdrawal(decimal amount, DateTime date, string


note)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount
of withdrawal must be positive");
}
Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance
- amount < _minimumBalance);
Transaction? withdrawal = new(-amount, date, note);
_allTransactions.Add(withdrawal);
if (overdraftTransaction != null)
_allTransactions.Add(overdraftTransaction);
}

protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)


{
if (isOverdrawn)
{
throw new InvalidOperationException("Not sufficient funds for
this withdrawal");
}
else
{
return default;
}
}

O método adicionado é protected , o que significa que ele pode ser chamado apenas
de classes derivadas. Essa declaração impede que outros clientes chamem o método. É
também virtual para que classes derivadas possam alterar o comportamento. O tipo
de retorno é Transaction? . A anotação ? indica que o método pode retornar null .
Adicione a seguinte implementação para LineOfCreditAccount cobrar uma taxa
quando o limite de saque for excedido:

C#

protected override Transaction? CheckWithdrawalLimit(bool isOver-


drawn) =>
isOverdrawn
? new Transaction(-20, DateTime.Now, "Apply overdraft fee")
: default;

A substituição retorna uma transação de taxa quando a conta é sacada. Se o saque


não ultrapassar o limite, o método retornará uma transação null . Isso indica que não
há nenhuma taxa. Teste essas alterações adicionando o seguinte código ao seu
método Main na classe Program :

C#

var lineOfCredit = new LineOfCreditAccount("line of credit", 0,


2000);
// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly
advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for
repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on
repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

Execute o programa e verifique os resultados.

Resumo
Se você não conseguir avançar, veja a origem deste tutorial em nosso repositório
GitHub .
Este tutorial demonstrou muitas das técnicas usadas na programação Orientada por
objeto:

Você usou Abstração quando definiu classes para cada um dos diferentes tipos de
conta. Essas classes descreveram o comportamento desse tipo de conta.
Você usou Encapsulamento quando manteve muitos detalhes private em cada
classe.
Você usou Herança quando aproveitou a implementação já criada na classe para
salvar o código BankAccount .
Você usou Polimorfismo ao criar métodos virtual que as classes derivadas
poderiam substituir para criar um comportamento específico para esse tipo de
conta.
6 Colaborar conosco no Comentários do .NET
GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto

Você também pode gostar