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