Principios SOLID en Go: teoría y práctica
SOLID es un acrónimo de cinco principios fundamentales de diseño orientado a objetos que ayudan a crear código flexible, mantenible y escalable. Aunque Go no es un lenguaje OOP clásico (no tiene herencia de clases), sus interfaces, composición y tipado estricto permiten aplicar SOLID con éxito. En este artículo, analizaremos cada principio con ejemplos del mundo real en el lenguaje Go.
1. Principio de Responsabilidad Única (SRP — Single Responsibility Principle)
Teoría: Cada tipo (estructura, función, interfaz) debe tener solo una razón para cambiar. En otras palabras, es responsable de una sola parte de la funcionalidad del programa.
Práctica en Go: Un error común es crear estructuras «divinas» que se encargan tanto de la lógica de negocio como del registro (logging) y el envío de notificaciones.
Ejemplo incorrecto (violación de SRP):
type OrderService struct{}
func (s *OrderService) Create(order Order) error { // 1. Guardar en BD // 2. Enviar email // 3. Registrar (log) return nil}Ejemplo correcto (cumplimiento de SRP):
type OrderRepository interface { Save(order Order) error}
type EmailService interface { Send(to string, subject string, body string) error}
type Logger interface { Info(msg string)}
type OrderService struct { repo OrderRepository email EmailService log Logger}
func (s *OrderService) Create(order Order) error { if err := s.repo.Save(order); err != nil { s.log.Info("Failed to save order") return err } if err := s.email.Send(order.Email, "Order Confirmed", "Your order is placed"); err != nil { s.log.Info("Failed to send email") } s.log.Info("Order created successfully") return nil}Ahora cada estructura es responsable solo de su tarea, y cambiar la lógica del email no afectará el guardado de pedidos.
2. Principio de Abierto/Cerrado (OCP — Open/Closed Principle)
Teoría: Las clases (tipos) deben estar abiertas para la extensión, pero cerradas para la modificación. Es decir, debe poder agregar nueva funcionalidad sin modificar el código existente.
Práctica en Go: Use interfaces y composición para agregar nuevas implementaciones sin cambiar la lógica principal.
Ejemplo: Supongamos que tenemos un sistema de cálculo de costo de envío. Inicialmente solo existe «envío estándar», pero necesitamos agregar «envío exprés».
// Definimos la interfaztype ShippingCostCalculator interface { Calculate(order Order) float64}
// Implementación estándartype StandardShipping struct{}
func (s StandardShipping) Calculate(order Order) float64 { return 5.99}
// Nueva implementación sin cambiar el código anteriortype ExpressShipping struct{}
func (e ExpressShipping) Calculate(order Order) float64 { return 15.99}
// Usofunc ProcessOrder(order Order, calculator ShippingCostCalculator) { cost := calculator.Calculate(order) fmt.Printf("Shipping cost: %.2f", cost)}No modificamos ProcessOrder — simplemente pasamos una nueva instancia de ExpressShipping. Esto es OCP.
3. Principio de Sustitución de Liskov (LSP — Liskov Substitution Principle)
Teoría: Los objetos en un programa deben ser reemplazables por instancias de sus subtipos sin alterar la corrección del programa. En Go, esto significa que si una función espera una interfaz, cualquier implementación de esa interfaz debe funcionar correctamente.
Práctica en Go: Asegúrese de que sus implementaciones de interfaces no violen las expectativas del código que las llama. Por ejemplo, si la interfaz supone que el método GetName() siempre dev