Skip to main content

Command Palette

Search for a command to run...

Why to learn Design Patterns šŸ¤” ?

Introduction to design patterns

Updated
•3 min read
Why to learn Design Patterns šŸ¤” ?

ā€œYou don’t learn design patterns because books say so —

you feel their need when your code becomes a tangled mess.
Let’s look at how one extra feature request can turn clean code into chaos — and how a pattern can save you.ā€

šŸ’­ Start with a Real Developer’s Struggle

Let’s take a very relatable example from my Java or Flutter experience.
Imagine this situation šŸ‘‡

🧩 Scenario: The ā€œSpaghetti Codeā€ Factory Problem

You’re building an eCommerce app.
At first, you only support Card Payments.

So, you write:

if (paymentType.equals("CARD")) {
    processCardPayment();
}

All good āœ…
But later, you add:

  • UPI Payment

  • Wallet Payment

  • Cash on Delivery

Your code becomes:

if (paymentType.equals("CARD")) {
    processCardPayment();
} else if (paymentType.equals("UPI")) {
    processUPIPayment();
} else if (paymentType.equals("WALLET")) {
    processWalletPayment();
} else if (paymentType.equals("COD")) {
    processCODPayment();
}

Looks okay?
Wait till the next requirement hits you…

ā€œWe need to add Crypto payment next week, and refund feature later.ā€

Now every time a new payment method comes, you’ll:

  • Modify this code again 😩

  • Risk breaking something else 😩

  • Have to test everything again 😩

This is code that grows horizontally (more if-else) — it violates the Open/Closed Principle.
Your code is open for modification when it should be open for extension.

🧠 And here comes the realization…

You pause and think:

ā€œWhy can’t I just have a flexible system that lets me add new payment methods without touching the core logic?ā€

That’s when you realize you need a Design Pattern.

āš™ļø Solution: Factory Method Pattern

You apply a Factory Method Pattern, which creates payment objects dynamically:

interface Payment {
    void process();
}

class CardPayment implements Payment {
    public void process() { System.out.println("Processing Card Payment"); }
}

class UPIPayment implements Payment {
    public void process() { System.out.println("Processing UPI Payment"); }
}

class PaymentFactory {
    public static Payment getPayment(String type) {
        return switch (type) {
            case "CARD" -> new CardPayment();
            case "UPI" -> new UPIPayment();
            default -> throw new IllegalArgumentException("Invalid payment type");
        };
    }
}

Now your main logic becomes:

Payment payment = PaymentFactory.getPayment("CARD");
payment.process();

If you add a new payment method tomorrow (like Crypto),
you create a new class, not change old ones.

āœ… Code is extensible
āœ… Clean and maintainable
āœ… No repeated conditionals

šŸ”„ Another Realistic Example: The Singleton Problem

Scenario:

You’re building a logging system in a backend app.
You notice that multiple parts of your app are writing to logs —
but different parts create their own logger objects.

You end up with:

  • Multiple log files,

  • Duplicate messages,

  • Messy output.

Then you realize —
you actually only need one logging instance shared across the entire app.

That’s when you use a Singleton pattern.