Unlock Go’s Structural & Architectural Patterns with Real-World Code
This guide explores key Go structural and architectural design patterns—Adapter, Decorator, Proxy, Bridge, Composite, Flyweight, Facade, Pipeline & Filter, Event Bus, Microkernel, and Dependency Injection—providing definitions, core ideas, Go implementations, and practical use cases to build scalable, maintainable systems.
Adapter Pattern — Compatibility with Legacy Interfaces
Definition: Convert an existing interface into one required by the client, allowing incompatible components to collaborate.
Core Idea: Introduce an interface‑conversion layer.
type Target interface {
Request()
}
type Adaptee struct{}
func (a *Adaptee) SpecificRequest() { fmt.Println("Adaptee special request") }
type Adapter struct {
adaptee *Adaptee
}
func (ad *Adapter) Request() {
ad.adaptee.SpecificRequest()
}Typical use cases: wrapping old third‑party libraries, normalising data formats across services.
Adapter is often the first choice for system integration because it avoids large‑scale refactoring.
Decorator Pattern — Dynamically Adding Behaviour
Definition: Attach additional responsibilities to an object without altering its structure.
Core Idea: Prefer composition over inheritance.
type Notifier interface { Send(msg string) }
type EmailNotifier struct{}
func (e *EmailNotifier) Send(msg string) { fmt.Println("Send email:", msg) }
type SMSDecorator struct { wrappee Notifier }
func (s *SMSDecorator) Send(msg string) {
s.wrappee.Send(msg)
fmt.Println("Send SMS:", msg)
}Use case: a notification pipeline that sends email, then SMS, then push notifications.
Features can be stacked indefinitely, providing flexible extension.
Proxy Pattern — Controlling Access and Adding Enhancements
Definition: Provide a surrogate object that controls access to the real subject.
Core Idea: Intercept calls without modifying the original object.
type Service interface { Request() }
type RealService struct{}
func (r *RealService) Request() { fmt.Println("Real service request") }
type Proxy struct { real *RealService }
func (p *Proxy) Request() {
fmt.Println("Proxy checks permission")
p.real.Request()
}Typical scenarios: RPC wrappers, caching proxies, security checks, lazy loading.
Commonly used to add logging or caching layers to gRPC calls.
Bridge Pattern — Decoupling Abstraction from Implementation
Definition: Separate an abstraction from its concrete implementation so both can evolve independently.
Core Idea: Two orthogonal dimensions of extension.
type MessageSender interface { Send(content string) }
type EmailSender struct{}
func (e *EmailSender) Send(content string) { fmt.Println("Send email:", content) }
type SMSSender struct{}
func (s *SMSSender) Send(content string) { fmt.Println("Send SMS:", content) }
type Message struct { sender MessageSender }
func (m *Message) Send(content string) { m.sender.Send(content) }Application: notification systems where message types (alert, marketing) and channels (email, SMS, webhook) can be extended independently.
Bridge enables unlimited dimensional expansion.
Composite Pattern — Managing Tree‑Like Structures
Definition: Compose objects into tree structures to represent part‑whole hierarchies.
Core Idea: Uniform interface for leaf and container nodes.
type Component interface { Operation() }
type Leaf struct { name string }
func (l *Leaf) Operation() { fmt.Println("Leaf:", l.name) }
type Composite struct { children []Component }
func (c *Composite) Add(child Component) { c.children = append(c.children, child) }
func (c *Composite) Operation() {
for _, child := range c.children {
child.Operation()
}
}Use cases: file‑system directories, organisational charts, menu systems.
Recursion provides a simple way to treat individual elements and whole structures uniformly.
Flyweight Pattern — Sharing Objects to Reduce Memory Usage
Definition: Use sharing to support large numbers of fine‑grained objects efficiently.
Core Idea: Combine an object pool with internal state sharing.
type Flyweight struct { name string }
type FlyweightFactory struct { pool map[string]*Flyweight }
func (f *FlyweightFactory) Get(name string) *Flyweight {
if v, ok := f.pool[name]; ok {
return v
}
fw := &Flyweight{name: name}
f.pool[name] = fw
return fw
}Typical examples: bullet pools in games, connection pools, cache pools.
Go's sync.Pool can implement the flyweight mechanism, reducing GC pressure.
Facade Pattern — Providing a Unified Interface
Definition: Offer a single, simplified interface to a complex subsystem.
Core Idea: Encapsulate subsystem complexity.
type Auth struct{}
func (a *Auth) Check() { fmt.Println("Validate identity") }
type Logger struct{}
func (l *Logger) Log() { fmt.Println("Record log") }
type API struct { auth *Auth; logger *Logger }
func (a *API) Serve() {
a.auth.Check()
a.logger.Log()
fmt.Println("Business logic completed")
}Applications: microservice gateways, SDKs with a simplified entry point.
Pipeline & Filter Pattern — Concurrent Stream Processing in Go
Definition: Split data processing into independent stages, each running in its own goroutine and linked by channels.
Core Idea: Stream processing combined with high‑concurrency decoupling.
func Filter(input <-chan int, fn func(int) bool) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for v := range input {
if fn(v) {
out <- v
}
}
}()
return out
}Use cases: log analysis, data cleaning, ETL pipelines, real‑time monitoring streams.
Go's channels make this pattern a powerful tool for building data‑flow systems.
Event Bus Pattern — Decoupled Component Communication
Definition: Event‑driven publish‑subscribe mechanism that lets modules communicate without direct references.
Core Idea: Pub‑sub + asynchronous dispatch.
type EventBus struct { subs map[string][]func(interface{}) }
func (bus *EventBus) Subscribe(topic string, fn func(interface{})) {
bus.subs[topic] = append(bus.subs[topic], fn)
}
func (bus *EventBus) Publish(topic string, data interface{}) {
for _, fn := range bus.subs[topic] {
go fn(data)
}
}Applications: event‑driven microservices, game event systems, plugin communication.
Can be backed by channels, Kafka, or other message queues for stronger guarantees.
Microkernel Architecture — Plugin‑Based Core System
Definition: Separate a stable core from extensible plugin modules.
Core Idea: Core stability combined with plugin decoupling.
Core module defines interfaces and provides the runtime framework.
Plugins implement those interfaces and are loaded dynamically via registration.
type Plugin interface { Name() string; Execute() }
type Kernel struct { plugins []Plugin }
func (k *Kernel) Register(p Plugin) { k.plugins = append(k.plugins, p) }
func (k *Kernel) RunAll() { for _, p := range k.plugins { p.Execute() } }Use cases: IDE plugin systems, monitoring agents, API gateway extensions.
Go's interfaces and reflection make it well‑suited for building microkernel architectures.
Dependency Injection — Decoupling Dependencies
Definition: Objects receive required collaborators from an external container rather than constructing them themselves.
Core Idea: Inversion of Control (IoC).
type Repository interface { Save(data string) }
type FileRepo struct{}
func (r *FileRepo) Save(data string) { fmt.Println("Save to file:", data) }
type Service struct { repo Repository }
func NewService(repo Repository) *Service { return &Service{repo: repo} }Typical scenario: web services injecting repositories, configuration, and logging components.
Popular Go DI libraries include wire , fx , and dig .
Summary
Structural patterns (Adapter, Decorator, Proxy, Bridge, Composite, Flyweight, Facade) provide decoupling, reuse, and memory efficiency through interfaces, composition, and embedding.
Architectural patterns (Pipeline & Filter, Event Bus, Microkernel, Dependency Injection) enable extensibility and modularity using goroutines, channels, plugin mechanisms, and IoC containers.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Wrench
Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
