Sistema bancario
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 106 de 107.
Desafío
Fácil¡Vamos a construir un Sistema Bancario que gestione cuentas, transacciones y operaciones financieras! Crearás un sistema robusto donde la integridad de los datos es crítica: los saldos de las cuentas solo deben cambiar a través de métodos validados, y los errores como sobregiros o montos inválidos deben manejarse con elegancia.
Organizarás tu código en cinco archivos:
account.go: Define una interfazTransactablecon los métodosDeposit(amount float64) erroryWithdraw(amount float64) error. Luego, crea dos tipos de cuenta que implementen esta interfaz:Una
CheckingAccountcon campos no exportadosid,holderybalance. Las cuentas corrientes permiten retiros que lleven el saldo exactamente a cero, pero no por debajo.Una
SavingsAccountcon los mismos campos no exportados más un campominBalance. Las cuentas de ahorro deben mantener un saldo mínimo de 100.0; los retiros que caigan por debajo de este umbral deben fallar.Ambos tipos de cuenta necesitan constructores (
NewCheckingAccountyNewSavingsAccount) y un método getterBalance() float64. Para los depósitos, rechaza montos negativos o cero. Para los retiros, rechaza montos negativos o cero y montos que excedan los fondos disponibles.transaction.go: Crea un structTransactionpara registrar la actividad de la cuenta. Cada transacción tiene unType(string: "deposit" o "withdrawal"),Amount(float64) yAccountID(string). Incluye un constructorNewTransactiony un métodoString() stringque devuelva el formato:[Type] $[Amount] on [AccountID]con el monto formateado a dos decimales.bank.go: Construye un structBankque gestione múltiples cuentas y registre transacciones. Almacena las cuentas en un mapa por ID y mantén un slice de transacciones. Implementa:NewBank() *Bank— inicializa colecciones vacíasAddAccount(id string, account Transactable)— registra una cuentaDeposit(accountID string, amount float64) error— deposita en una cuenta y registra la transacción en caso de éxitoWithdraw(accountID string, amount float64) error— retira de una cuenta y registra la transacción en caso de éxitoTransfer(fromID, toID string, amount float64) error— mueve dinero entre cuentas de forma atómica (si el retiro tiene éxito pero el depósito falla, revierte el retiro)GetBalance(accountID string) (float64, error)— devuelve el saldo de una cuenta
Devuelve los errores apropiados para cuentas inexistentes.
errors.go: Define errores centinela para tus operaciones bancarias:ErrAccountNotFound— cuando un ID de cuenta no existeErrInsufficientFunds— cuando el retiro excede el saldo disponibleErrInvalidAmount— cuando el monto del depósito/retiro es cero o negativoErrMinBalanceRequired— cuando un retiro de ahorros violaría el saldo mínimo
main.go: Crea un banco y procesa operaciones basadas en la entrada.Lee el número de cuentas a crear. Para cada cuenta, lee el tipo de cuenta (
checkingosavings), el ID, el nombre del titular y el saldo inicial. Crea el tipo de cuenta apropiado y añádela al banco.Luego lee el número de operaciones. Cada operación es una de:
deposit [accountID] [amount]withdraw [accountID] [amount]transfer [fromID] [toID] [amount]balance [accountID]
Para cada operación, imprime el resultado:
OKpara depósitos/retiros/transferencias exitosos, el saldo formateado a dos decimales para consultas de saldo, oError: [message]para fallos utilizando tus mensajes de error centinela.
Se proporcionarán las siguientes entradas:
- Número de cuentas, luego el tipo de cada cuenta, ID, titular, saldo inicial (4 líneas cada una)
- Número de operaciones, luego cada operación (1 línea cada una)
Por ejemplo, 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 ACC002Tu salida debería ser:
OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00Y dado:
1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50Tu salida debería ser:
OK
0.00
Error: invalid amount
Error: account not foundObserva cómo la interfaz Transactable permite al Banco trabajar con ambos tipos de cuenta de manera uniforme, mientras que cada tipo de cuenta impone sus propias reglas. El método Transfer demuestra operaciones atómicas: si algo sale mal a mitad del proceso, ¡el sistema se mantiene consistente!
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leer el número de cuentas
scanner.Scan()
numAccounts, _ := strconv.Atoi(scanner.Text())
// Crear un nuevo banco
bank := NewBank()
// Leer y crear cuentas
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: Crear el tipo de cuenta apropiado basado en accountType
// y añadirlo al banco
_ = accountType
_ = id
_ = holder
_ = balance
}
// Leer el número de operaciones
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// Procesar cada operación
for i := 0; i < numOps; i++ {
scanner.Scan()
parts := strings.Fields(scanner.Text())
operation := parts[0]
// TODO: Manejar cada tipo de operación:
// - "deposit": depositar en la cuenta, imprimir "OK" o error
// - "withdraw": retirar de la cuenta, imprimir "OK" o error
// - "transfer": transferir entre cuentas, imprimir "OK" o error
// - "balance": obtener e imprimir el saldo formateado a 2 decimales
_ = operation
}
}
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool