Labs ICT
โญ Pro Login

Refactoring to Design Patterns

Recognizing code smells and refactoring toward proven design patterns.

What is Refactoring?

Refactoring is the process of restructuring existing code without changing its external behavior. The goal is to improve code readability, reduce complexity, and make it easier to maintain โ€” all while keeping the tests passing.

Refactoring to design patterns means recognizing code smells (signs of poor design) and applying the appropriate pattern to fix them. It's not about adding features โ€” it's about making the code better at what it already does.

Common Code Smells

1. Long Method
   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
   A method that does too many things.
   Solution: Extract Method, Strategy, Command

2. Large Class
   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
   A class with too many responsibilities.
   Solution: Extract Class, SRP

3. Switch Statements / Type Checking
   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
   Long if/else or switch based on type.
   Solution: Polymorphism, Strategy, State

4. Duplicated Code
   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
   The same logic appears in multiple places.
   Solution: Extract Method, Template Method

5. Feature Envy
   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
   A method uses more data from another class than its own.
   Solution: Move Method

6. Data Clumps
   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
   Same groups of data appearing together repeatedly.
   Solution: Extract Class

7. Primitive Obsession
   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
   Using primitives instead of small objects.
   Solution: Replace with Value Object

Refactoring to Strategy

// BEFORE: Switch statement on type
class ShippingCalculator {
  calculate(order, shippingType) {
    switch (shippingType) {
      case "standard":
        return order.weight * 0.5 + 5;
      case "express":
        return order.weight * 1.5 + 15;
      case "overnight":
        return order.weight * 3 + 30;
      case "international":
        return order.weight * 2 + 25 + order.customsFee;
      default:
        throw new Error("Unknown shipping type");
    }
  }
}

// AFTER: Refactored to Strategy
class ShippingStrategy {
  calculate(order) { throw new Error("Implement me"); }
}

class StandardShipping extends ShippingStrategy {
  calculate(order) {
    return order.weight * 0.5 + 5;
  }
}

class ExpressShipping extends ShippingStrategy {
  calculate(order) {
    return order.weight * 1.5 + 15;
  }
}

class OvernightShipping extends ShippingStrategy {
  calculate(order) {
    return order.weight * 3 + 30;
  }
}

class InternationalShipping extends ShippingStrategy {
  calculate(order) {
    return order.weight * 2 + 25 + order.customsFee;
  }
}

class ShippingCalculator {
  calculate(order, strategy) {
    return strategy.calculate(order);
  }
}

// Usage
const calculator = new ShippingCalculator();
const order = { weight: 10, customsFee: 50 };

console.log(calculator.calculate(order, new StandardShipping()));  // 10
console.log(calculator.calculate(order, new ExpressShipping()));  // 30
console.log(calculator.calculate(order, new InternationalShipping())); // 95

// Benefits:
// โœ“ Adding new shipping types doesn't modify existing code (OCP)
// โœ“ Each strategy is small and focused (SRP)
// โœ“ No more switch statement to maintain

Refactoring to Observer

// BEFORE: Tight coupling โ€” Order directly calls multiple services
class Order {
  place() {
    // Business logic...
    this.status = "placed";

    // Directly coupled to multiple services
    inventoryService.updateStock(this.items);
    emailService.sendConfirmation(this.customer);
    analyticsService.trackOrder(this);
    loyaltyService.addPoints(this.customer, this.total);
  }
}

// AFTER: Refactored to Observer
class Order {
  #eventEmitter;

  constructor() {
    this.#eventEmitter = new EventEmitter();
  }

  on(event, handler) {
    this.#eventEmitter.on(event, handler);
  }

  place() {
    // Business logic only
    this.status = "placed";
    this.#eventEmitter.emit("orderPlaced", this);
  }
}

// Setup observers (loose coupling)
const order = new Order();

order.on("orderPlaced", (o) => inventoryService.updateStock(o.items));
order.on("orderPlaced", (o) => emailService.sendConfirmation(o.customer));
order.on("orderPlaced", (o) => analyticsService.trackOrder(o));
order.on("orderPlaced", (o) => loyaltyService.addPoints(o.customer, o.total));

// Benefits:
// โœ“ Order doesn't know about inventory, email, analytics, loyalty
// โœ“ New observers can be added without modifying Order
// โœ“ Observers can be removed or reordered independently

Refactoring to Factory

// BEFORE: Constructor with type-based branching
class Notification {
  constructor(type, recipient, message) {
    this.message = message;
    this.recipient = recipient;

    if (type === "email") {
      this.send = () => console.log(`Email to ${recipient}: ${message}`);
    } else if (type === "sms") {
      this.send = () => console.log(`SMS to ${recipient}: ${message}`);
    } else if (type === "push") {
      this.send = () => console.log(`Push to ${recipient}: ${message}`);
    } else {
      throw new Error("Unknown notification type");
    }
  }
}

// AFTER: Refactored to Factory Method
class Notification {
  send(message) { throw new Error("Implement send()"); }
}

class EmailNotification extends Notification {
  #recipient;
  constructor(recipient) { super(); this.#recipient = recipient; }
  send(message) {
    console.log(`Email to ${this.#recipient}: ${message}`);
  }
}

class SMSNotification extends Notification {
  #recipient;
  constructor(recipient) { super(); this.#recipient = recipient; }
  send(message) {
    console.log(`SMS to ${this.#recipient}: ${message}`);
  }
}

class PushNotification extends Notification {
  #deviceId;
  constructor(deviceId) { super(); this.#deviceId = deviceId; }
  send(message) {
    console.log(`Push to ${this.#deviceId}: ${message}`);
  }
}

class NotificationFactory {
  static create(type, recipient) {
    switch (type) {
      case "email": return new EmailNotification(recipient);
      case "sms": return new SMSNotification(recipient);
      case "push": return new PushNotification(recipient);
      default: throw new Error("Unknown type");
    }
  }
}

// Usage
const notification = NotificationFactory.create("email", "user@example.com");
notification.send("Hello!");

Refactoring Checklist

Before Refactoring:
  โœ“ Ensure you have tests that verify current behavior
  โœ“ Make sure tests pass before you start
  โœ“ Commit your working code (safe rollback point)

During Refactoring:
  โœ“ Make one small change at a time
  โœ“ Run tests after every change
  โœ“ Keep methods small and focused
  โœ“ Rename for clarity
  โœ“ Extract when a method does too much

After Refactoring:
  โœ“ All tests pass
  โœ“ Code is more readable
  โœ“ No new code smells introduced
  โœ“ Commit with a clear message

Common Refactoring Patterns:
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚ Code Smell             โ”‚ Refactoring Pattern         โ”‚
  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
  โ”‚ Long method            โ”‚ Extract Method / Strategy   โ”‚
  โ”‚ Large class            โ”‚ Extract Class               โ”‚
  โ”‚ Switch on type         โ”‚ Polymorphism / Strategy     โ”‚
  โ”‚ Duplicated code        โ”‚ Extract / Template Method   โ”‚
  โ”‚ Feature envy           โ”‚ Move Method                 โ”‚
  โ”‚ Data clumps            โ”‚ Extract Class / Value Objectโ”‚
  โ”‚ Null checks everywhere โ”‚ Null Object Pattern         โ”‚
  โ”‚ God class              โ”‚ Facade / SRP                โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿงช Quick Quiz

What is refactoring?