Encapsulamiento a nivel de paquete
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy. Lección 48 de 107.
Aunque la lección anterior abordó la visibilidad a nivel de campo, la encapsulación de Go en realidad funciona a nivel de paquete. Todo lo que se encuentra dentro del mismo paquete, independientemente del archivo en el que esté, puede acceder a campos y funciones no exportados.
Considera un paquete con varios archivos:
// account/account.go
package account
type Account struct {
balance int // no exportado
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // funciona - mismo paquete
}
Ambos archivos pertenecen al paquete account, por lo que helper.go puede acceder directamente al campo no exportado balance. Esto es intencional. Te permite dividir la lógica del paquete entre varios archivos manteniendo el acceso interno.
Sin embargo, el código fuera del paquete no puede acceder a miembros no exportados:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // funciona - método exportado
// acc.balance = 50 // error - campo no exportado
// account.ResetAccount // funciona si ResetAccount estuviera exportado
}
Este límite a nivel de paquete es el principal mecanismo de encapsulación de Go. Diseñas los paquetes como unidades cohesivas en las que el código interno colabora libremente, mientras que el código externo interactúa únicamente mediante la API exportada. Piensa en cada paquete como un módulo independiente con una interfaz pública clara.
Desafío
FácilConstruyamos un sistema de gestión de inventory que demuestre cómo varios archivos dentro del mismo paquete pueden colaborar accediendo directamente a fields no Exported, mientras mantienen esos datos ocultos para el código externo.
Crearás tres archivos que trabajan juntos en el paquete main:
product.go: Define una estructuraProductcon un fieldNameExported y un fieldquantityno Exported (int). Añade un methodInfo() stringExported que devuelva el format:[Name]: [quantity] in stockinventory.go: Crea functions helper que trabajen con products accediendo directly al field no Exportedquantity(es posible porque están en el mismo paquete). Implementa:Restock(p *Product, amount int)- añade el amount a la cantidad del productoSell(p *Product, amount int) bool- resta el amount de quantity si hay suficiente stock, y devuelvetruesi tiene éxito ofalsesi no hay enough stockNewProduct(name string, initialQty int) *Product- constructor que creates un producto con el nombre y la cantidad initial dados
main.go: Lee los detalles del producto desde la entrada, crea un producto usando el constructor, realiza operaciones de inventory y muestra los resultados. Después de cada operación, imprime la información del producto para mostrar el estado actualizado.
Se proporcionarán las siguientes entradas:
- Línea 1: Nombre del producto
- Línea 2: Cantidad initial (entero)
- Línea 3: Cantidad de reabastecimiento (entero)
- Línea 4: Cantidad de venta (entero)
Después de crear el producto, imprime su información initial. Después, reabastécelo e imprime la información de nuevo. Finalmente, intenta vender y muestra la información actualizada (si tiene éxito) o Sale failed: insufficient stock (si no hay suficiente cantidad).
Por ejemplo, dados Laptop, 10, 5 y 8, tu salida debería ser:
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stockY dados Phone, 3, 2 y 10, tu salida debería ser:
Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stockLa idea clave aquí es que inventory.go puede leer y modificar directly el field no Exported quantity porque está en el mismo paquete que product.go. Esta es la encapsulación a nivel de paquete en acción: la colaboración interna ocurre libremente, mientras los datos permanecen protegidos frente a otros paquetes.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer el nombre del producto
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
// Leer la cantidad inicial
initialQtyStr, _ := reader.ReadString('\n')
initialQty, _ := strconv.Atoi(strings.TrimSpace(initialQtyStr))
// Leer la cantidad de reposición
restockStr, _ := reader.ReadString('\n')
restockAmount, _ := strconv.Atoi(strings.TrimSpace(restockStr))
// Leer la cantidad a vender
sellStr, _ := reader.ReadString('\n')
sellAmount, _ := strconv.Atoi(strings.TrimSpace(sellStr))
// TODO: Crear un nuevo producto usando el constructor NewProduct
// TODO: Imprimir la información inicial del producto
// TODO: Reponer el producto e imprimir la información
// TODO: Intentar vender e imprimir el resultado
// Si la venta tiene éxito, imprimir la información actualizada
// If sale fails, print "Sale failed: insufficient stock"
fmt.Println() // Reemplazar con la salida real
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
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 PoolPractica por tu cuenta: Compilador de Go online