Introdução: Por que o SOLID é importante em Go?
SOLID é um conjunto de cinco princípios fundamentais da programação orientada a objetos que ajudam a criar código flexível, de fácil manutenção e extensível. Embora Go não seja uma linguagem OOP clássica (não possui classes ou herança), esses princípios se aplicam perfeitamente graças a interfaces, composição e tipagem forte. Neste artigo, vamos detalhar cada princípio SOLID com exemplos em Go, para que você possa escrever código de maior qualidade e mais escalável.
1. Princípio da Responsabilidade Única (SRP)
O SRP afirma: "Um módulo deve ter apenas um motivo para mudar". Em Go, isso significa que uma função, struct ou pacote deve ser responsável por uma tarefa claramente definida.
Exemplo ruim (violação do SRP):
type Report struct {
Title string
Body string
}
func (r *Report) Generate() string {
return fmt.Sprintf("<h1>%s</h1><p>%s</p>", r.Title, r.Body)
}
func (r *Report) SaveToFile(filename string) error {
return os.WriteFile(filename, []byte(r.Generate()), 0644)
}
func (r *Report) SendEmail(addr string) error {
// email sending logic
return nil
}
Aqui a struct Report é responsável por geração, salvamento e envio — três motivos diferentes para mudar.
Exemplo bom (conformidade com SRP):
type Report struct {
Title string
Body string
}
func (r *Report) Generate() string {
return fmt.Sprintf("<h1>%s</h1><p>%s</p>", r.Title, r.Body)
}
type ReportSaver struct{}
func (rs *ReportSaver) SaveToFile(report *Report, filename string) error {
return os.WriteFile(filename, []byte(report.Generate()), 0644)
}
type EmailSender struct{}
func (es *EmailSender) Send(report *Report, addr string) error {
// email sending logic
return nil
}
Agora cada entidade tem uma única responsabilidade: geração, salvamento e envio.
2. Princípio Aberto/Fechado (OCP)
O OCP afirma: "Entidades de software devem estar abertas para extensão, mas fechadas para modificação". Em Go, isso é alcançado através de interfaces e composição.
Exemplo ruim (violação do OCP):
type PaymentProcessor struct{}
func (p *PaymentProcessor) Process(paymentType string, amount float64) {
if paymentType == "credit_card" {
// credit card processing
} else if paymentType == "paypal" {
// PayPal processing
} else if paymentType == "crypto" {
// cryptocurrency processing
}
}
Ao adicionar um novo tipo de pagamento, você teria que modificar o método Process.
Exemplo bom (conformidade com OCP):
type PaymentMethod interface {
Pay(amount float64) error
}
type CreditCard struct{}
func (c *CreditCard) Pay(amount float64) error {
// card payment logic
return nil
}
type PayPal struct{}
func (p *PayPal) Pay(amount float64) error {
// PayPal payment logic
return nil
}
type PaymentProcessor struct{}
func (pp *PaymentProcessor) Process(method PaymentMethod, amount float64) error {
return method.Pay(amount)
}
Agora, para adicionar um novo método de pagamento, basta implementar a interface PaymentMethod sem modificar o código existente.
3. Princípio da Substituição de Liskov (LSP)
O LSP exige: "Objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem alterar a correção do programa". Em Go, isso diz respeito à implementação de interfaces.
Exemplo ruim (violação do LSP):
type Bird interface {
Fly() error
}
type Penguin struct{}
func (p *Penguin) Fly() error {
return errors.New("penguins cannot fly")
}
func LetBirdFly(b Bird) {