Menu
Coddy logo textTech

Sistema Bancário

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 106 de 107.

challenge icon

Desafio

Fácil

Vamos 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 interface Transactable com os métodos Deposit(amount float64) error e Withdraw(amount float64) error. Em seguida, crie dois tipos de conta que implementam essa interface:

    Uma CheckingAccount com os campos não exportados id, holder e balance. Contas correntes permitem saques que deixam o saldo exatamente em zero, mas não abaixo disso.

    Uma SavingsAccount com os mesmos campos não exportados, além de um campo minBalance. 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 (NewCheckingAccount e NewSavingsAccount) e um método getter Balance() 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 struct Transaction para registrar a atividade da conta. Cada transação possui um Type (string: "deposit" ou "withdrawal"), Amount (float64) e AccountID (string). Inclua um construtor NewTransaction e um método String() string que retorne o formato: [Type] $[Amount] on [AccountID] com o valor formatado com duas casas decimais.
  • bank.go: Construa uma struct Bank que 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 vazias
    • AddAccount(id string, account Transactable) — registra uma conta
    • Deposit(accountID string, amount float64) error — deposita em uma conta e registra a transação em caso de sucesso
    • Withdraw(accountID string, amount float64) error — saca de uma conta e registra a transação em caso de sucesso
    • Transfer(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 existe
    • ErrInsufficientFunds — quando o saque excede o saldo disponível
    • ErrInvalidAmount — quando o valor do depósito/saque é zero ou negativo
    • ErrMinBalanceRequired — 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 (checking ou savings), 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: OK para depósitos/saques/transferências bem-sucedidos, o saldo formatado com duas casas decimais para consultas de saldo, ou Error: [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 ACC002

Sua saída deve ser:

OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00

E dado:

1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50

Sua saída deve ser:

OK
0.00
Error: invalid amount
Error: account not found

Observe 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