Princípios SOLID em Go: nível avançado
SOLID são cinco princípios fundamentais de design orientado a objetos que ajudam a criar código flexível, sustentável e escalável. No contexto do Go, uma linguagem com a filosofia única de composição em vez de herança, a aplicação do SOLID requer uma abordagem especial. Neste artigo, analisaremos cada princípio em nível avançado, com exemplos reais e antipadrões característicos da comunidade Go.
1. Single Responsibility Principle (SRP) em Go
Princípio da responsabilidade única afirma: uma classe (struct) deve ter apenas um motivo para mudar. Em Go, isso é frequentemente violado em pacotes utilitários e "objetos divinos".
Antipadrão: struct com múltiplas responsabilidades
type UserService struct { db *sql.DB cache *redis.Client logger *log.Logger}
func (s *UserService) CreateUser(ctx context.Context, u User) error { // 1. Validação // 2. Hash de senha // 3. Salvar no BD // 4. Enviar email // 5. Log return nil}Solução: divida as responsabilidades em serviços separados e use composição.
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 agora tem um motivo para mudar, simplificando testes e manutenção.
2. Open-Closed Principle (OCP) através de interfaces
Princípio aberto/fechado: entidades devem estar abertas para extensão, mas fechadas para modificação. Em Go, isso é alcançado através de interfaces e injeção de dependências.
Exemplo ruim: acoplamento rígido
type PaymentProcessor struct { paypalAPI *PayPalAPI}
func (p *PaymentProcessor) Process(amount float64) error { return p.paypalAPI.Charge(amount)}Se precisar adicionar Stripe — terá que alterar o código.
Abordagem correta: interface + estratégia
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}}Agora é possível adicionar novos gateways de pagamento sem alterar o PaymentService.
3. Liskov Substitution Principle (LSP) no contexto do Go
Princípio da substituição de Barbara Liskov: objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem alterar a correção do programa. Em Go, onde não há herança clássica, o LSP se manifesta através de contratos de interfaces.
Violação típica: interface com métodos redundantes
type Bird interface { Fly() error Swim() error}
type Penguin struct{}
func (p *Penguin) Fly() error { return errors.New("penguins can't fly")}
func (p *Penguin) Swim() error { return nil}Aqui, Penguin não pode implementar corretamente Fly(), violando o LSP.
Solução: separação de interfaces
type Flyer interface { Fly() error}
type Swimmer interface { S