At some point in our careers we’ve all been tangled in if-else nests and duct-taped functions just trying to make things work. But what if I told you there’s a better way? A way used by seasoned developers to write cleaner, smarter, and more maintainable code?
We’ve all heard the term “design patterns” thrown around — usually by that one senior dev who speaks fluent Lorem ipsum.
Maybe you’ve even noticed how certain built-in functions or framework methods just make sense — like they were crafted with some secret blueprint. Well, spoiler alert: they were. That blueprint is what we call a design pattern — a time-tested solution to a common software design problem. In this article, I'll break down the Top 5 Design Patterns you’ve probably seen in the wild (even if you didn’t know their names), and show you how to use them like a pro.
1. Strategy Pattern
Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable.
Use When:
You have multiple ways of doing something (e.g., sorting, payment, compression).
You want to switch algorithms at runtime.
Structure:
Strategy Interface
Concrete Strategies
Context (uses Strategy)
Sample Code
// Strategy
interface PaymentStrategy {
void pay(int amount);
}
// Concrete Strategy
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " using credit card");
}
}
class PayPalPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " using PayPal");
}
}
// Context
class ShoppingCart {
private PaymentStrategy strategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void checkout(int amount) {
strategy.pay(amount);
}
}
2. Observer Pattern
Intent: Define a one-to-many dependency so when one object (subject) changes state, all its dependents (observers) are notified.
Use When:
An object changes state and others need to know.
For event-driven systems (e.g., UI, stock price updates).
Structure:
Subject
Observer Interface
Concrete Observers
Sample Code
interface Observer {
void update(String message);
}
class EmailSubscriber implements Observer {
public void update(String message) {
System.out.println("Email received: " + message);
}
}
class Publisher {
private List<Observer> observers = new ArrayList<>();
public void subscribe(Observer obs) {
observers.add(obs);
}
public void notifyObservers(String message) {
for (Observer obs : observers) {
obs.update(message);
}
}
}
3. Decorator Pattern
Intent: Attach additional responsibilities to an object dynamically. It’s like wrapping an object with extra behavior.
Use When:
You want to add features without changing the base class.
Avoid subclass explosion.
Structure:
Component Interface
Concrete Component
Decorator (implements component and wraps another)
Sample Code
interface Coffee {
String getDescription();
int cost();
}
class BasicCoffee implements Coffee {
public String getDescription() { return "Basic Coffee"; }
public int cost() { return 50; }
}
class MilkDecorator implements Coffee {
private Coffee coffee;
public MilkDecorator(Coffee coffee) { this.coffee = coffee; }
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public int cost() {
return coffee.cost() + 10;
}
}
4. Factory Pattern
Intent: Create objects without exposing the instantiation logic to the client. Use a method to create the object.
Use When:
You want to delegate object creation to a separate method/class.
Need to avoid tight coupling.
Structure:
Interface or Superclass
Factory method to create instances
Sample Code
interface Animal {
void speak();
}
class Dog implements Animal {
public void speak() {
System.out.println("Woof!");
}
}
class AnimalFactory {
public static Animal getAnimal(String type) {
if (type.equals("dog")) return new Dog();
throw new IllegalArgumentException("Unknown type");
}
}
5. Singleton Pattern
Intent: Ensure a class has only one instance and provide a global access point to it.
Use When:
You need only one instance (e.g., DB connection, config).
To coordinate actions across the system.
Structure:
Private constructor
Static instance variable
Public static getter method
Sample Code
class ConfigManager {
private static ConfigManager instance;
private ConfigManager() {}
public static ConfigManager getInstance() {
if (instance == null) {
instance = new ConfigManager();
}
return instance;
}
}
Thread Safe Version:
public class ThreadSafeSingleton {
private static final ThreadSafeSingleton instance = new ThreadSafeSingleton();
private ThreadSafeSingleton() {}
public static ThreadSafeSingleton getInstance() {
return instance;
}
}
Here’s a quick table to help you map the purpose of each pattern to its core concept.
Pattern | Purpose | Key Concept |
---|---|---|
Strategy | Swap behavior like changing themes | Algorithms packed neatly in separate boxes |
Observer | Alert everyone when something changes | One-to-many notification chain |
Decorator | Add features without changing the original object | Wrap it, stack it, reuse it |
Factory | Don’t new up stuff everywhere |
One method to create them all |
Singleton | Only one allowed in the club | One instance, globally accessed |
So whether you’re building a game, an API, or the next unicorn startup from your garage, these patterns will save you time, headaches, and therapy bills.
Top comments (0)