Back to Blog
Object-Oriented Programming in Go: Pragmatic LLD Patterns
Published on June 5, 2026 By Aunmoy Dey Tanmoy

Object-Oriented Programming in Go: Pragmatic LLD Patterns

Go OOP Design Patterns Clean Code

Object-Oriented Programming in Go ( LLD )

Encapsulation (Go’s MOST MISUNDERSTOOD TOPIC)

FeatureComposition (logger Logger)Embedding (Logger)
Relationship”Has-a” (User has a logger)“Behaves-like” (User acts like a logger)
Accesss.logger.Log()s.Log()
VisibilityHidden from the outsidePublicly “promoted”
Primary UseInjecting DependenciesWrapping/Extending behavior

🎯 The “Senior” Rule of Thumb

  • Use Composition by default. It is safer, more explicit, and follows the “Principle of Least Privilege.” It prevents your structs from having 50 methods you never intended to expose.
  • Use Embedding only when you want the outer struct to “inherit” the API of the inner struct. If you find yourself writing ten “wrapper” methods that just call the inner struct, that’s a signal to switch to embedding.

Interface, Polymerphism and Abstraction


type UserService  struct {
    credential *ClientCredential //This is Composition 
    dB     *sql.DB
}

What is Tight Coupling ? Here db is tightly coupled , UserService Says it will only Use SQL db . But what if I want to integrate both redis and pgsql than? Also mock testing is hard . This is tight coupling and that’s why we use Interphase to decouple to overcome this problem .


type UserService  struct {
    credential *ClientCredential //This is Composition 
   repository     UserRepository
}


type UserRepository interface {

    Save(client ClientCredentials) error
}
UserService says: “I don’t care WHAT DB you use, Just give me something that can Save” Now I canintegrate postgres and redis and other dbDecoupling means removing direct dependency on concrete implementations


type ClientCredentials struct {

    ID     string
    Role   string

    mu     sync.RWMutex
    apiKey string
}



func (c *ClientCredentials) RotateKey(newKey string) error {
    if len(newKey) < 10 {
        return errors.New("weak key")
    }

    c.mu.Lock()
    defer c.mu.Unlock()

    c.apiKey = newKey
    return nil
}

Abstraction means hiding implementation details behind an interface (what it does, not how).