Thumbnail for Stop Writing If-Else Trees: Embrace the State Pattern Instead

Stop Writing If-Else Trees: Embrace the State Pattern Instead

0
547 views

Table of Contents

Clean code always looks like it was written by someone who cares.” – Robert C. Martin

We've all been there.

You're working on a feature that needs to behave differently based on a user’s settings or an app’s current mode. Maybe it’s how a smartphone handles incoming calls—ring, vibrate, or stay silent. So you reach for what seems like the obvious tool: the if-else or switch tree.

But here’s the problem: as conditions grow, these trees become tangled messes. Suddenly your code is bloated, hard to test, and a nightmare to extend.

There’s a better way: the State Pattern.


📉 The Problem With If-Else Trees

Let’s say we’re building a phone app with three modes:

  • Normal: Ring loudly.

  • Vibrate: Buzz silently.

  • Silent: Do nothing.

You might start with something like this:

void onReceiveCall() {
  if (mode == 'normal') {
    print("Ring ring!");
  } else if (mode == 'vibrate') {
    print("Bzzzt...");
  } else if (mode == 'silent') {
    print("...");
  }
}

At first glance, this looks fine. But what happens when:

  • You need to handle other events like messages or alarms?

  • Each state needs to track internal data (like vibrate mode counting missed calls)?

  • You want to add a Do Not Disturb mode?

Each of these scenarios turns your neat if-else block into a brittle, sprawling mess.


🎯 Enter the State Pattern

The State Pattern lets an object alter its behavior when its internal state changes, removing the need for explicit conditionals.

Let’s break this down into components:

  1. Context (e.g., Phone): The main class that delegates behavior.

  2. State Interface (e.g., PhoneState): Declares methods for different behaviors.

  3. Concrete States (e.g., NormalState, VibrateState): Define specific behaviors.

  4. Transition Logic: Each state can decide when and how to switch to another.


📱 State Pattern Example in JavaScript

// State Interface
class PhoneState {
  onReceiveCall(context) {
    throw new Error("Must override onReceiveCall");
  }
}

// Normal State
class NormalState extends PhoneState {
  onReceiveCall(context) {
    console.log("📞 Ring ring! (Normal mode)");
  }
}

// Vibrate State
class VibrateState extends PhoneState {
  constructor() {
    super();
    this.missedCalls = 0;
  }

  onReceiveCall(context) {
    this.missedCalls++;
    console.log("🤫 Bzzzt... (Vibrate mode)");

    if (this.missedCalls >= 3) {
      console.log("🔕 Switching to Silent mode due to missed calls.");
      context.setState(new SilentState());
    }
  }
}

// Silent State
class SilentState extends PhoneState {
  onReceiveCall(context) {
    console.log("🙊 ... (Silent mode)");
  }
}

// Context
class Phone {
  constructor() {
    this.state = new NormalState();
  }

  setState(newState) {
    this.state = newState;
  }

  receiveCall() {
    this.state.onReceiveCall(this);
  }
}

🧠 Why It’s Better

  • Cleaner, modular code

    • ⚠️ Avoids bloated if-else or switch logic scattered across your codebase

  • Easy to extend

    • ⚠️ You can add new states or behaviors without modifying existing logic

  • Follows SOLID principles

    • ⚠️ Especially adheres to the Open/Closed Principle — open for extension, closed for modification

  • Encapsulated behavior

    • ⚠️ Each state contains its own logic, reducing complexity in the main class


⚖️ When Not to Use the State Pattern

While powerful, this pattern isn’t always the right tool:

  • If you only have two or three states and minimal complexity, if-else might be simpler.

  • If your states don’t have much unique logic, extra classes may just clutter your code.

  • Be cautious of creating too many trivial classes.

Use it when behavior per state is complex and state transitions are frequent or dynamic.


🚀 Final Thoughts

The State Pattern is more than a design technique—it’s a mindset shift. Instead of hard-coding behavior into conditionals, you delegate responsibility to specialized objects that can evolve independently.

This leads to cleaner, testable, extensible code—the kind you’ll thank yourself for later.

So next time you find yourself writing a growing if-else chain, stop and ask:

Can this be a State?
If yes, break that tree and go State.

About the Author

1 Comment