SOLID in Go

SOLID Principles in Go

SOLID is often associated with Java/OOP. However, its core principles (decoupling, cohesion) apply perfectly to Go, just implemented differently.

S: Single Responsibility Principle (SRP)

“A package should have one reason to change.”

Go enforces this with its package system. * Bad: package utils (The junk drawer). * Good: package hash, package user, package http.

O: Open/Closed Principle

“Open for extension, closed for modification.”

In Go, we achieve this via Interfaces. If a function accepts an interface, you can extend its behavior by passing a new struct that satisfies the interface, without modifying the function.

type Shape interface { Area() float64 }

func TotalArea(shapes []Shape) float64 {
    var total float64
    for _, s := range shapes {
        total += s.Area()
    }
    return total
}

You can add Triangle later without touching TotalArea.

L: Liskov Substitution Principle

“Subtypes must be substitutable for their base types.”

In Go, this is implicit. If *MyWriter satisfies io.Writer, it is an io.Writer. However, you must ensure behavioral compatibility (e.g., don’t panic in a Write method).

I: Interface Segregation Principle

“Clients should not be forced to depend on methods they do not use.”

This is the most important rule in Go. * Java: Define big interfaces upfront (IUser). * Go: Define tiny interfaces where you use them.

Bad:

type User interface {
    Save()
    Delete()
    SendEmail()
    Validate()
}

func Emailer(u User) { ... } // Forces dependence on Save/Delete

Good:

type EmailSender interface {
    SendEmail()
}

func Emailer(e EmailSender) { ... } // Only depends on what it needs

D: Dependency Inversion Principle

“Depend on abstractions, not concretions.”

Don’t accept structs; accept interfaces.

Bad:

func NewService(db *sql.DB) // Concretion

Good:

type Datastore interface {
    Query(string) Rows
}

func NewService(db Datastore) // Abstraction

This allows you to swap sql.DB for a Mock DB during testing.

Summary

SOLID in Go is simpler than in OOP. * S: Small packages. * O / D: Accept interfaces, return structs. * I: Define 1-method interfaces locally.