Sistema Bancário
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy. Lição 106 de 107.
Desafio
FácilVamos criar um Sistema Bancário que gerencia contas, transações e operações financeiras! Você criará um sistema robusto em que a integridade dos dados é crítica: os saldos das contas só devem mudar por meio de métodos validados, e erros como saques que excedem o saldo ou valores inválidos devem ser tratados de forma adequada.
Você organizará seu código em cinco arquivos:
account.go: Defina uma interfaceTransactablecom os métodosDeposit(amount float64) erroreWithdraw(amount float64) error. Em seguida, crie dois tipos de conta que implementem essa interface:Uma
CheckingAccountcom os campos não exportadosid,holderebalance. Contas correntes permitem saques que reduzem o saldo exatamente a zero, mas não abaixo disso.Uma
SavingsAccountcom os mesmos campos não exportados, além de um campominBalance. Contas poupança devem manter um saldo mínimo de 100.0: saques que reduziriam o saldo abaixo desse limite devem falhar.Ambos os tipos de conta precisam de construtores (
NewCheckingAccounteNewSavingsAccount) e de um método getterBalance() float64. Para depósitos, rejeite valores negativos ou iguais a zero. Para saques, rejeite valores negativos ou iguais a zero e valores que excedam os fundos disponíveis.transaction.go: Crie uma structTransactionpara registrar a atividade das contas. Cada transação possui umType(string: "deposit" ou "withdrawal"), umAmount(float64) e umAccountID(string). Inclua um construtorNewTransactione um métodoString() stringque retorne o formato:[Type] $[Amount] on [AccountID], com o valor formatado para duas casas decimais.bank.go: Crie uma structBankque gerencie várias contas e registre transações. Armazene as contas em um map por ID e mantenha uma slice de transações. Implemente:NewBank() *Bank: inicializa coleções vaziasAddAccount(id string, account Transactable): registra uma contaDeposit(accountID string, amount float64) error: deposita em uma conta e registra a transação em caso de sucessoWithdraw(accountID string, amount float64) error: realiza um saque de uma conta e registra a transação em caso de sucessoTransfer(fromID, toID string, amount float64) error: move dinheiro entre contas atomicamente (se o saque for bem-sucedido, mas o depósito falhar, reverta o saque)GetBalance(accountID string) (float64, error): retorna o saldo de uma conta
Retorne erros apropriados para contas inexistentes.
errors.go: Defina erros sentinela para suas operações bancárias:ErrAccountNotFound: quando um ID de conta não existeErrInsufficientFunds: quando o saque excede o saldo disponívelErrInvalidAmount: quando o valor do depósito/saque é igual a zero ou negativoErrMinBalanceRequired: quando um saque de uma conta poupança violaria o saldo mínimo
main.go: Crie um banco e processe operações com base na entrada.Leia o número de contas a serem criadas. Para cada conta, leia o tipo da conta (
checkingousavings), o ID, o nome do titular e o saldo inicial. Crie o tipo de conta apropriado e adicione-o ao banco.Em seguida, leia o número de operações. Cada operação é uma destas:
deposit [accountID] [amount]withdraw [accountID] [amount]transfer [fromID] [toID] [amount]balance [accountID]
Para cada operação, imprima o resultado:
OKpara depósitos/saques/transferências bem-sucedidos, o saldo formatado para duas casas decimais em consultas de saldo ouError: [message]para falhas, usando suas mensagens de erro sentinela.
As seguintes entradas serão fornecidas:
- Número de contas, seguido do tipo, ID, titular e saldo inicial de cada conta (4 linhas para cada uma)
- Número de operações, seguido de cada operação (1 linha para cada uma)
Por exemplo, considerando:
2
checking
ACC001
Alice
500.00
savings
ACC002
Bob
1000.00
6
deposit ACC001 200
balance ACC001
withdraw ACC002 950
withdraw ACC001 800
transfer ACC001 ACC002 100
balance ACC002Sua saída deve ser:
OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00E considerando:
1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50Sua saída deve ser:
OK
0.00
Error: invalid amount
Error: account not foundObserve como a interface Transactable permite que o Bank trabalhe uniformemente com os dois tipos de conta, enquanto cada tipo de conta impõe suas próprias regras. O método Transfer demonstra operações atômicas: se algo der errado no meio do processo, o sistema permanece consistente!
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Ler o número de contas
scanner.Scan()
numAccounts, _ := strconv.Atoi(scanner.Text())
// Criar um novo banco
bank := NewBank()
// Ler e criar contas
for i := 0; i < numAccounts; i++ {
scanner.Scan()
accountType := scanner.Text()
scanner.Scan()
id := scanner.Text()
scanner.Scan()
holder := scanner.Text()
scanner.Scan()
balance, _ := strconv.ParseFloat(scanner.Text(), 64)
// TODO: Criar o tipo de conta apropriado com base em accountType
// e adicioná-lo ao banco
_ = accountType
_ = id
_ = holder
_ = balance
}
// Ler o número de operações
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// Processar cada operação
for i := 0; i < numOps; i++ {
scanner.Scan()
parts := strings.Fields(scanner.Text())
operation := parts[0]
// TODO: Lidar com cada tipo de operação:
// - "deposit": depositar na conta, imprimir "OK" ou erro
// - "withdraw": sacar da conta, imprimir "OK" ou erro
// - "transfer": transferir entre contas, imprimir "OK" ou erro
// - "balance": obter e imprimir o saldo formatado com 2 casas decimais
_ = operation
}
}
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online