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.
Mientras que la lección anterior cubrió la visibilidad a nivel de campo, la encapsulación de Go en realidad opera a nivel de paquete. Todo lo que esté dentro del mismo paquete —independientemente del archivo en el que se encuentre— puede acceder a campos y funciones no exportados.
Considera un paquete con múltiples 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 balance no exportado. Esto es intencional: permite dividir la lógica del paquete en varios archivos manteniendo el acceso interno.
Sin embargo, el código fuera del paquete no puede acceder a los 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 // funcionaría si ResetAccount estuviera exportado
}
Este límite a nivel de paquete es el mecanismo de encapsulación principal de Go. Diseñas los paquetes como unidades cohesivas donde el código interno colabora libremente, mientras que el código externo interactúa solo a través de la API exportada. Piensa en cada paquete como un módulo autónomo con una interfaz pública clara.
Desafío
FácilVamos a construir un sistema de gestión de inventario que demuestre cómo múltiples archivos dentro del mismo paquete pueden colaborar accediendo directamente a campos no exportados, 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 campo exportadoNamey un campo no exportadoquantity(int). Añade un método exportadoInfo() stringque devuelva el formato:[Name]: [quantity] in stockinventory.go: Crea funciones auxiliares que trabajen con productos accediendo directamente al campo no exportadoquantity(posible porque están en el mismo paquete). Implementa:Restock(p *Product, amount int)- añade la cantidad alquantitydel productoSell(p *Product, amount int) bool- resta la cantidad dequantitysi existe stock suficiente, devuelvetruesi tiene éxito ofalsesi no hay suficiente stockNewProduct(name string, initialQty int) *Product- constructor que crea un producto con el nombre y la cantidad inicial dados
main.go: Lee los detalles del producto de la entrada, crea un producto usando el constructor, realiza operaciones de inventario 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 inicial (entero)
- Línea 3: Cantidad a reponer (entero)
- Línea 4: Cantidad a vender (entero)
Después de crear el producto, imprime su información inicial. Luego repón el stock e imprime la información de nuevo. Finalmente, intenta vender e imprime 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 directamente el campo no exportado quantity porque está en el mismo paquete que product.go. Esto es la encapsulación a nivel de paquete en acción: la colaboración interna ocurre libremente mientras los datos permanecen protegidos de paquetes externos.
Hoja de referencia
El encapsulamiento en Go opera a nivel de paquete, no a nivel de archivo. Todos los archivos dentro del mismo paquete pueden acceder a campos y funciones no exportados, independientemente del archivo en el que estén definidos.
Acceso a Nivel de Paquete
Los miembros no exportados son accesibles en todos los archivos del mismo paquete:
// account/account.go
package account
type Account struct {
balance int // unexported
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // works - same package
}
Restricciones de Paquetes Externos
El código fuera del paquete no puede acceder a los miembros no exportados:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // works - exported method
// acc.balance = 50 // error - unexported field
}
Este diseño le permite dividir la lógica del paquete en varios archivos mientras mantiene el acceso interno, creando unidades cohesivas con interfaces públicas claras.
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 reabastecimiento
restockStr, _ := reader.ReadString('\n')
restockAmount, _ := strconv.Atoi(strings.TrimSpace(restockStr))
// Leer la cantidad de venta
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: Reabastecer el producto e imprimir la información
// TODO: Intentar vender e imprimir el resultado
// Si la venta tiene éxito, imprimir la información actualizada
// Si la venta falla, imprimir "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 Pool