Principios SOLID en Go: nivel avanzado
SOLID son cinco principios fundamentales del diseño orientado a objetos que ayudan a crear código flexible, mantenible y escalable. En el contexto de Go, un lenguaje con una filosofía única de composición en lugar de herencia, la aplicación de SOLID requiere un enfoque especial. En este artículo, analizaremos cada principio a nivel avanzado, con ejemplos reales y antipatrones característicos de la comunidad Go.
1. Principio de Responsabilidad Única (SRP) en Go
El principio de responsabilidad única establece: una clase (estructura) debe tener solo una razón para cambiar. En Go, esto a menudo se viola en paquetes utilitarios y "objetos divinos".
Antipatrón: estructura con múltiples responsabilidades
type UserService struct { db *sql.DB cache *redis.Client logger *log.Logger}
func (s *UserService) CreateUser(ctx context.Context, u User) error { // 1. Validación // 2. Hash de contraseña // 3. Guardar en BD // 4. Enviar email // 5. Registro en log return nil}Solución: divida las responsabilidades en servicios separados y use composición.
type UserValidator interface { Validate(u User) error}
type PasswordHasher interface { Hash(password string) (string, error)}
type UserRepository interface { Save(ctx context.Context, u User) error}
type EmailSender interface { SendWelcomeEmail(ctx context.Context, email string) error}
type UserService struct { validator UserValidator hasher PasswordHasher repo UserRepository emailSender EmailSender logger Logger}Cada componente ahora tiene una sola razón para cambiar, lo que simplifica las pruebas y el mantenimiento.
2. Principio Abierto-Cerrado (OCP) mediante interfaces
El principio abierto/cerrado: las entidades deben estar abiertas para extensión, pero cerradas para modificación. En Go, esto se logra mediante interfaces e inyección de dependencias.
Ejemplo incorrecto: acoplamiento rígido
type PaymentProcessor struct { paypalAPI *PayPalAPI}
func (p *PaymentProcessor) Process(amount float64) error { return p.paypalAPI.Charge(amount)}Si necesita agregar Stripe, tendrá que modificar el código.
Enfoque correcto: interfaz + estrategia
type PaymentGateway interface { Charge(amount float64) error Refund(transactionID string) error}
type PayPalGateway struct{ /* ... */ }type StripeGateway struct{ /* ... */ }
func (p *PayPalGateway) Charge(amount float64) error { /* ... */ }func (s *StripeGateway) Charge(amount float64) error { /* ... */ }
type PaymentService struct { gateway PaymentGateway}
func NewPaymentService(gw PaymentGateway) *PaymentService { return &PaymentService{gateway: gw}}Ahora puede agregar nuevas pasarelas de pago sin modificar PaymentService.
3. Principio de Sustitución de Liskov (LSP) en el contexto de Go
El principio de sustitución de Barbara Liskov: los objetos en un programa deben ser reemplazables por instancias de sus subtipos sin alterar la corrección del programa. En Go, donde no hay herencia clásica, LSP se manifiesta a través de contratos de interfaces.
Violación típica: interfaz con métodos redundantes
type Bird interface { Fly() error Swim() error}
type Penguin struct{}
func (p *Penguin) Fly() error { return errors.New("los pingüinos no pueden volar")}
func (p *Penguin) Swim() error { return nil}Aquí, Penguin no puede implementar correctamente Fly(), lo que viola LSP.
Solución: separación de interfaces
type Flyer interface { Fly() error}
type Swimmer interface { S