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 construir um Sistema Bancário que gerencia contas, transações e operações financeiras! Você criará um sistema robusto onde a integridade dos dados é crítica — os saldos das contas devem mudar apenas por meio de métodos validados, e erros como saques a descoberto ou valores inválidos devem ser tratados adequadamente.
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 implementam essa interface:Uma
CheckingAccountcom os campos não exportadosid,holderebalance. Contas correntes permitem saques que deixam o saldo exatamente em 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 fariam o saldo cair abaixo desse limite devem falhar.Ambos os tipos de conta precisam de construtores (
NewCheckingAccounteNewSavingsAccount) e um método getterBalance() float64. Para depósitos, rejeite valores negativos ou zero. Para saques, rejeite valores negativos ou zero e valores que excedam os fundos disponíveis.transaction.go: Crie uma structTransactionpara registrar a atividade da conta. Cada transação possui umType(string: "deposit" ou "withdrawal"),Amount(float64) eAccountID(string). Inclua um construtorNewTransactione um métodoString() stringque retorne o formato:[Type] $[Amount] on [AccountID]com o valor formatado com duas casas decimais.bank.go: Construa uma structBankque gerencia múltiplas contas e registra transações. Armazene as contas em um mapa por ID e mantenha um 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— saca de uma conta e registra a transação em caso de sucessoTransfer(fromID, toID string, amount float64) error— move dinheiro entre contas de forma atômica (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 é zero ou negativoErrMinBalanceRequired— quando um saque da poupança violaria o saldo mínimo
main.go: Crie um banco e processe as operações com base na entrada.Leia o número de contas a serem criadas. Para cada conta, leia o tipo de conta (
checkingousavings), ID, nome do titular e 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 de:
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 com duas casas decimais para consultas de saldo, ouError: [message]para falhas usando suas mensagens de erro sentinela.
As seguintes entradas serão fornecidas:
- Número de contas, depois o tipo de cada conta, ID, titular, saldo inicial (4 linhas cada)
- Número de operações, depois cada operação (1 linha cada)
Por exemplo, dado:
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 dado:
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 Banco trabalhe com ambos os tipos de conta de forma uniforme, 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 caminho, 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á-la 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 ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool