Labs ICT
⭐ Pro Login

GRASP Patterns

General Responsibility Assignment Software Patterns for assigning class responsibilities.

What is GRASP?

GRASP (General Responsibility Assignment Software Patterns) is a set of guidelines for assigning responsibilities to classes in OO design. Created by Craig Larman, GRASP provides nine patterns that help you make fundamental design decisions about where to put behavior and data.

Think of GRASP as a "first principles" approach to OO design. Before you reach for design patterns, master GRASP β€” it tells you how to think about assigning responsibilities.

The 9 GRASP Patterns

1. Information Expert
   Assign a responsibility to the class that has the information
   needed to fulfill it.

2. Creator
   Assign class B the responsibility to create class A if B
   aggregates, contains, records, or closely uses A.

3. Controller
   Assign responsibility for handling a system event to a class
   that represents the overall system, a use case, or a device.

4. Low Coupling
   Assign responsibilities so that coupling between classes remains low.

5. High Cohesion
   Assign responsibilities so that each class has a focused, coherent purpose.

6. Polymorphism
   Use polymorphism when you have conditional behavior based on type.

7. Pure Fabrication
   Introduce an artificial class when you need a high-cohesion,
   low-coupling solution but no domain class fits.

8. Indirection
   Introduce an intermediary to mediate between two components.

9. Protected Variations
   Identify points of likely change and assign responsibilities
   to create a stable interface around them.

Information Expert

The most fundamental pattern: assign a responsibility to the class that has the information needed to carry it out.

// Question: Where should we put calculateTotal()?
// Answer: In the class that KNOWS the items β€” Order.

class Order {
  #items = [];

  addItem(item) {
    this.#items.push(item);
  }

  // Information Expert: Order HAS the items, so it knows the total
  calculateTotal() {
    return this.#items.reduce(
      (sum, item) => sum + item.price * item.quantity, 0
    );
  }

  getItemCount() {
    return this.#items.length;
  }
}

class OrderItem {
  constructor(name, price, quantity) {
    this.name = name;
    this.price = price;
    this.quantity = quantity;
  }

  // Information Expert: OrderItem knows its OWN line total
  getLineTotal() {
    return this.price * this.quantity;
  }
}

const order = new Order();
order.addItem(new OrderItem("Laptop", 999, 1));
order.addItem(new OrderItem("Mouse", 25, 2));
console.log(order.calculateTotal()); // 1049

Creator

Assign class B the responsibility to create class A when B aggregates, contains, records, or closely uses A. This promotes cohesion because the creating class already knows what the created class needs.

// Who should create OrderItems? Order β€” because it contains them.

class Order {
  #items = [];

  // Creator: Order contains OrderItems, so it creates them
  createItem(name, price, quantity) {
    const item = new OrderItem(name, price, quantity);
    this.#items.push(item);
    return item;
  }

  getItems() {
    return [...this.#items];
  }
}

class OrderItem {
  constructor(name, price, quantity) {
    this.name = name;
    this.price = price;
    this.quantity = quantity;
  }
}

const order = new Order();
order.createItem("Keyboard", 75, 1);
order.createItem("Monitor", 300, 2);

Controller

Assign a class to handle system events. The controller acts as the entry point between the UI and the domain layer.

// Controller: handles system events from the UI

class OrderController {
  #orderService;
  #notificationService;

  constructor(orderService, notificationService) {
    this.#orderService = orderService;
    this.#notificationService = notificationService;
  }

  // Handles the "place order" event from the UI
  async handlePlaceOrder(orderData) {
    try {
      const order = await this.#orderService.createOrder(orderData);
      await this.#notificationService.sendConfirmation(order);
      return { success: true, orderId: order.id };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  // Handles the "cancel order" event
  async handleCancelOrder(orderId) {
    try {
      await this.#orderService.cancelOrder(orderId);
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }
}

Low Coupling & High Cohesion

These two patterns work together to create maintainable designs:

Low Coupling:
  Minimize dependencies between classes.
  Changes in one class should have minimal impact on others.

  High Coupling (bad):
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚Order   │───▢│Payment │───▢│Inventoryβ”‚
    β”‚        │───▢│Gateway │───▢│System  β”‚
    β”‚        │───▢│Email   │───▢│Databaseβ”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    Order depends on everything!

  Low Coupling (good):
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Order  │────────▢│ OrderService    β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚ (coordinates)   β”‚
                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    Order only knows about OrderService.

High Cohesion:
  Each class should have a focused, coherent set of responsibilities.

  Low Cohesion (bad):
    class SystemManager {
      handleOrders() {}
      processPayments() {}
      sendEmails() {}
      generateReports() {}
      backupDatabase() {}
    }
    Does too many unrelated things!

  High Cohesion (good):
    class OrderProcessor {
      processOrder(order) {}
      validateOrder(order) {}
    }
    Focused on one thing: orders.

Polymorphism & Protected Variations

Polymorphism:
  Use polymorphism when you have conditional logic based on type.

  // Instead of this:
  function calculateDiscount(type, amount) {
    if (type === "regular") return amount * 0.05;
    if (type === "premium") return amount * 0.10;
    if (type === "vip") return amount * 0.20;
  }

  // Do this:
  class DiscountPolicy {
    calculate(amount) { throw new Error("Implement me"); }
  }
  class RegularDiscount extends DiscountPolicy {
    calculate(amount) { return amount * 0.05; }
  }
  class PremiumDiscount extends DiscountPolicy {
    calculate(amount) { return amount * 0.10; }
  }
  class VipDiscount extends DiscountPolicy {
    calculate(amount) { return amount * 0.20; }
  }

Protected Variations:
  Shield elements from variations in other elements.
  Create stable interfaces around points of likely change.

  // If payment gateways might change:
  class PaymentGateway {
    charge(amount) { throw new Error("Implement me"); }
  }

  class StripeGateway extends PaymentGateway {
    charge(amount) { /* Stripe API */ }
  }

  class PayPalGateway extends PaymentGateway {
    charge(amount) { /* PayPal API */ }
  }
  // Switching gateways doesn't affect OrderService

πŸ§ͺ Quick Quiz

Which GRASP pattern states that information experts should be assigned the responsibility to do the work?