Princípios SOLID em Go: teoria e prática
SOLID é um acrônimo de cinco princípios fundamentais de design orientado a objetos que ajudam a criar código flexível, sustentável e escalável. Embora Go não seja uma linguagem OOP clássica (não possui herança de classes), suas interfaces, composição e tipagem forte permitem aplicar SOLID com sucesso. Neste artigo, vamos analisar cada princípio com exemplos do mundo real em Go.
1. Princípio da Responsabilidade Única (SRP — Single Responsibility Principle)
Teoria: Cada tipo (struct, função, interface) deve ter apenas um motivo para mudar. Em outras palavras, ele é responsável por apenas uma parte da funcionalidade do programa.
Prática em Go: Um erro comum é criar structs "divinas" que lidam com lógica de negócios, logging e envio de notificações ao mesmo tempo.
Exemplo ruim (violação do SRP):
type OrderService struct{}
func (s *OrderService) Create(order Order) error { // 1. Salvar no banco de dados // 2. Enviar email // 3. Logging return nil}Exemplo bom (respeitando o 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}Agora cada struct é responsável apenas por sua tarefa, e alterar a lógica do email não afetará o salvamento dos pedidos.
2. Princípio do Aberto/Fechado (OCP — Open/Closed Principle)
Teoria: Classes (tipos) devem estar abertas para extensão, mas fechadas para modificação. Ou seja, você deve ser capaz de adicionar nova funcionalidade sem alterar o código existente.
Prática em Go: Use interfaces e composição para adicionar novas implementações sem alterar a lógica principal.
Exemplo: Suponha que temos um sistema de cálculo de custo de frete. Inicialmente, existe apenas "frete padrão", mas precisamos adicionar "frete expresso".
// Definindo a interfacetype ShippingCostCalculator interface { Calculate(order Order) float64}
// Implementação padrãotype StandardShipping struct{}
func (s StandardShipping) Calculate(order Order) float64 { return 5.99}
// Nova implementação sem alterar o código acimatype 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)}Não alteramos ProcessOrder — apenas passamos uma nova instância de ExpressShipping. Isso é o OCP.
3. Princípio da Substituição de Liskov (LSP — Liskov Substitution Principle)
Teoria: Objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem alterar a correção do programa. Em Go, isso significa que se uma função espera uma interface, qualquer implementação dessa interface deve funcionar corretamente.
Prática em Go: Certifique-se de que suas implementações de interface não violem as expectativas do código chamador. Por exemplo, se a interface pressupõe que o método GetName() sempre retorna