Design patterns are not academic theory⦠they are practical tools that help you write code that is:
- Easier to maintain π§Ή
- Easier to scale π
- Easier to understand π
π As a Software Engineer, mastering design patterns is what separates:
writing code that works β vs designing systems that last π₯
π What Are Design Patterns?
Design patterns are reusable solutions to common problems in software design.
π‘ Important:
They are NOT copy-paste code β they are ways of thinking and structuring solutions
π§© Types of Design Patterns
There are 3 main categories:
ποΈ Creational
How objects are created
π Example: Singleton, Factory
π Structural
How objects are composed
π Example: Adapter, Decorator
βοΈ Behavioral
How objects interact
π Example: Observer, Strategy
π₯ Most Common Design Patterns (With Real Examples)
Letβs focus on the ones youβll actually use π
1οΈβ£ Singleton π§ββοΈ
π What It Is
Ensures that a class has only one instance.
π§ When to Use It
- Database connections
- Configuration
- Logging
π» Example (Go)
var instance *DB
var once sync.Once
func GetDB() *DB {
once.Do(func() {
instance = &DB{}
})
return instance
}
β οΈ Common Problem
- Harder to test
- Introduces global state
π Use it carefully
2οΈβ£ Factory π
π What It Is
Encapsulates object creation logic.
π§ When to Use It
When you need to create different types of objects based on conditions.
π» Example
type Payment interface {
Pay(amount float64)
}
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) {}
type PayPal struct{}
func (p PayPal) Pay(amount float64) {}
func PaymentFactory(method string) Payment {
switch method {
case "card":
return CreditCard{}
case "paypal":
return PayPal{}
default:
return nil
}
}
π‘ Benefits
- Avoids scattered
if/elselogic - Centralizes object creation
3οΈβ£ Strategy π―
π What It Is
Allows you to switch behavior dynamically.
π§ When to Use It
When you have multiple ways to perform an operation.
π» Example
type DiscountStrategy interface {
Apply(price float64) float64
}
type NoDiscount struct{}
func (d NoDiscount) Apply(price float64) float64 {
return price
}
type TenPercent struct{}
func (d TenPercent) Apply(price float64) float64 {
return price * 0.9
}
Usage:
func Calculate(price float64, strategy DiscountStrategy) float64 {
return strategy.Apply(price)
}
π‘ Benefits
- Flexible
- Easy to extend
- Clean separation of logic
4οΈβ£ Observer π
π What It Is
Allows multiple objects to react to an event.
π§ When to Use It
- Event-driven systems
- Notifications
- Messaging systems
π» Example
type Observer interface {
Update(data string)
}
type EmailService struct{}
func (e EmailService) Update(data string) {}
type EventManager struct {
observers []Observer
}
func (em *EventManager) Notify(data string) {
for _, o := range em.observers {
o.Update(data)
}
}
π‘ Benefits
- Low coupling
- Scalable event handling
5οΈβ£ Decorator π
π What It Is
Adds behavior to objects without modifying them.
π§ When to Use It
- Middleware
- Logging
- Metrics
π» Example
type Handler func()
func LoggingMiddleware(next Handler) Handler {
return func() {
fmt.Println("Start")
next()
fmt.Println("End")
}
}
π‘ Real Use Case
π Middleware in APIs (very common in Go, Node.js, etc.)
6οΈβ£ Adapter π
π What It Is
Allows incompatible interfaces to work together.
π§ When to Use It
- Integrating third-party APIs
- Legacy system migration
π» Example
type OldService struct{}
func (o OldService) GetData() string {
return "data"
}
type NewInterface interface {
Fetch() string
}
type Adapter struct {
old OldService
}
func (a Adapter) Fetch() string {
return a.old.GetData()
}
π‘ Benefits
- Keeps existing code intact
- Simplifies integrations
π§ How to Use These Patterns in Real Life
π« Common Mistake
Trying to use patterns everywhere:
β Overengineering
β Unnecessary complexity
β Hard-to-maintain code
β Better Approach
Use patterns when:
- You see duplication
- Code is growing in complexity
- You need flexibility
β‘ Real Backend Example
Letβs say youβre building a payment system:
π You can combine:
- Factory β Create payment method
- Strategy β Apply pricing/discount logic
- Observer β Send notifications
π₯ Result:
A flexible and scalable system
π§ Pro Tips (Senior-Level Thinking)
π― 1. Focus on Problems, Not Patterns
Good:
βI have this problem β which pattern solves it?β
Bad:
βI want to use this pattern no matter whatβ
π 2. Refactor Into Patterns
- Start simple
- Apply patterns when needed
π§© 3. Combine Patterns
Real systems use multiple patterns together.
π 4. Keep It Simple
A strong engineer:
- Uses fewer patterns
- But uses them correctly
π Final Thoughts
Design patterns are:
π§ Mental models
βοΈ Proven solutions
π Key to building scalable systems
π₯ The Real Difference
A junior developer:
writes code that works
A senior engineer:
designs systems that evolve