Design patterns: the developer toolbox 🛠️
Ah, design patterns. Those tiny jewels of software wisdom that make us look smarter than we really are. But what are they, exactly? And why should you care? Imagine building a house without a plan. Sure, you might pull it off, but you will probably end up with a leaking roof and a door that refuses to close. Design patterns are the blueprints for your code: proven solutions to common problems. Not rigid recipes, but adaptable structures.
This guide focuses on code-level design patterns. If you are looking for the larger system view with distributed systems, CQRS, microservices, and their usual drama, that is a different layer of design entirely.
A bit of history 📜
The concept of design patterns became mainstream with Design Patterns: Elements of Reusable Object-Oriented Software, written by the famous Gang of Four: Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. That book gave software teams a shared vocabulary to describe recurring solutions to recurring problems.
Each pattern identifies a generalized problem and associates it with a reusable solution. The point is not worshipping the pattern. The point is recognizing when a known structure helps you stop reinventing the same wheel with a slightly different wobble.
Why use design patterns? 🤔
- Reusability: why reinvent the wheel when a tested solution already exists?
- Maintainability: clearer, better organized code causes fewer headaches for you and whoever inherits the project.
- Communication: saying “we can use Strategy here” is faster than explaining the full idea from scratch.
- Proven solutions: design patterns come from years of real-world practice, not from a particularly inspired whiteboard session.
The three main categories 🧭
The Gang of Four grouped design patterns into three macro categories. The lists below are not exhaustive; they cover the most representative ones.
1. Creational patterns 🏗️
These patterns focus on object creation, isolating construction logic from the rest of the code.
- Singleton: ensures that a type has a single shared instance.
- Factory Method: defines an interface for creating objects while leaving the concrete decision elsewhere.
- Abstract Factory: creates families of related objects without exposing their concrete classes.
- Builder: assembles complex objects step by step.
- Prototype: creates new objects by cloning an existing instance.
A note on Singleton in Go
In Go you rarely implement a classical Singleton because there are no classes in the OO sense. Most of the time package-level initialization or sync.Once gives you the same result in a simpler and safer way:
|
|
Factory Method example in Go
|
|
2. Structural patterns 🧱
These patterns deal with composition, making systems more flexible and easier to evolve.
- Adapter: lets two incompatible interfaces work together.
- Bridge: separates an abstraction from its implementation so both can evolve independently.
- Composite: treats individual and grouped objects uniformly in tree structures.
- Decorator: adds behavior dynamically to an object.
- Facade: provides a simplified interface to a more complex subsystem.
- Flyweight: reduces memory usage by sharing common state.
- Proxy: controls access to another object through a surrogate.
Decorator example in Go
|
|
3. Behavioral patterns 🎭
These patterns focus on interaction, responsibilities, and how objects collaborate.
- Observer: automatically notifies dependents when state changes.
- Strategy: swaps algorithms dynamically.
- Command: wraps a request as an object.
- State: changes behavior based on internal state.
- Template Method: defines the shell of an algorithm and delegates some steps.
- Visitor: separates an algorithm from the data structure it operates on.
- Chain of Responsibility: passes a request along a chain until someone handles it.
- Mediator: centralizes communication and reduces mesh-like dependencies.
- Memento: externalizes and restores state without breaking encapsulation.
- Iterator: gives you a uniform way to traverse a collection.
- Interpreter: defines a grammar and an interpreter for simple languages.
Strategy example in Go
|
|
The pattern becomes interesting when the Context can really switch strategy at runtime: pricing policies, serializers, compression algorithms, ranking logic, and so on.
Useful resources 📚
- Refactoring Guru: a broad guide to design patterns with practical examples.
- Christopher Okhravi on YouTube: clear explanations with good pacing and no mystical aura.
- Wikipedia - Design Pattern: a solid overview if you want the formal framing.
When not to use them 🚧
- Over-engineering: if a 20-line function is enough, do not introduce 5 interfaces just to feel sophisticated.
- Too early: applying a pattern before the recurring problem actually exists usually makes the design stiffer.
- Too much abstraction: too many layers make debugging feel like archaeology.
- Mechanical copying: understand the why before the how.
Golden rule: start simple, then extract the pattern when the recurring problem becomes visible. Pattern literacy is useful. Pattern cosplay is less so.