GRASP — General Responsibility Assignment Software Patterns — is a set of nine principles introduced by Craig Larman in 'Applying UML and Patterns' (1997). Where SOLID tells you how to structure individual class relationships, GRASP addresses a more fundamental question that every developer faces at design time: which object should be responsible for this behavior? GRASP provides named heuristics for answering that question systematically rather than by intuition. The patterns are not algorithms — they are lenses that make the reasoning process explicit and communicable.
Information Expert is the most frequently applied GRASP pattern and the source of the most common design question: 'Where should I put this method?' The answer: in the class that has the data the method needs. If calculating an order total requires knowing the line items, the Order class (which holds the items) should calculate its own total — not an external service that reaches inside Order to extract the items.
// ── VIOLATION: behavior separated from the data it needs ────────
class OrderPricingService { Money calculateTotal(Order order) {return order.getItems().stream() // reaching into Order
.map(i -> i.getPrice().multiply(i.getQuantity())) // using Order's data
.reduce(Money.ZERO, Money::add);
}
}
// ── FIX: assign responsibility to the class with the information ─
class Order {private final List<OrderLineItem> items;
Money total() {return items.stream()
.map(OrderLineItem::subtotal) // delegate to each item's expert
.reduce(Money.ZERO, Money::add);
}
}
class OrderLineItem {private final Money price;
private final int quantity;
Money subtotal() { return price.multiply(quantity); } // expert for its own data}
The Controller pattern answers: who should handle a system event (a user action, an incoming message, a scheduled trigger)? Not the domain objects — they should be ignorant of delivery mechanisms. Not the UI — it should be ignorant of business logic. The Controller is a non-UI class that receives events and delegates to domain objects. It coordinates but does not compute. In practice: a REST controller is a GRASP Controller. So is a message consumer, a CLI command handler, or a batch job scheduler — all are controllers for different delivery mechanisms.
// ── GRASP Controller: delegates, does not compute ───────────────
@RestController
class PlaceOrderController {private final PlaceOrderService service;
@PostMapping("/orders") ResponseEntity<OrderResponse> place(@Valid @RequestBody PlaceOrderRequest req) {// Controller responsibilities: receive, validate format, map, delegate
PlaceOrderCommand cmd = req.toCommand(); // map input
Order order = service.placeOrder(cmd); // delegate ALL logic
return ResponseEntity.ok(OrderResponse.from(order)); // map output
// NO business logic here — zero domain rules in the controller
}
}
// ── Same domain, different controller (Kafka consumer) ──────────
@KafkaListener(topics = "order-requests")
class PlaceOrderEventController {private final PlaceOrderService service;
void handle(PlaceOrderEvent event) {service.placeOrder(event.toCommand()); // same service, different entry point
}
}
The Polymorphism pattern addresses type-based behavioral variation. When you see a switch or if/else on a type field, that is usually a signal that the Polymorphism pattern applies: each branch of the conditional should be a subtype, and the behavior should be dispatched by the type system — not by the caller. This is directly related to OCP: the switch grows with every new type; polymorphism means adding a new type adds a new class without touching existing code.
// ── VIOLATION: type switch that grows with every new discount ────
Money applyDiscount(Order order, DiscountType type) { return switch (type) {case PERCENTAGE -> order.total().multiply(0.9);
case FIXED -> order.total().subtract(Money.of(10));
case BOGO -> order.total().multiply(0.5); // added later — modified method
};
}
// ── FIX: each type encapsulates its own behavior ─────────────────
interface DiscountPolicy { Money apply(Money total); }record PercentageDiscount(double rate) implements DiscountPolicy { public Money apply(Money total) { return total.multiply(1 - rate); }}
record FixedDiscount(Money amount) implements DiscountPolicy { public Money apply(Money total) { return total.subtract(amount); }}
record BogoDiscount() implements DiscountPolicy { public Money apply(Money total) { return total.multiply(0.5); }}
// Caller — never changes when new discount types are added:
Money discounted = policy.apply(order.total());
Larman called Protected Variations 'the most fundamental principle in software design' — and it is hard to argue otherwise. Every design pattern, every architectural style, every abstraction exists to protect stable code from volatile change. The practical application: identify the points of instability in your system (external APIs, business rules that change frequently, database technology, third-party libraries) and wrap each in a stable interface. The code that depends on the interface is protected — it will not change when the instability changes.
Pure Fabrication resolves the tension between high cohesion and the need to put infrastructure concerns somewhere. Consider persistence: the Order entity should not know how to save itself to a database — that would violate SRP and introduce infrastructure into the domain. But the persistence logic must live somewhere. A Repository is a Pure Fabrication: a class invented for design purposes that has no direct counterpart in the problem domain. It exists to give persistence behavior a high-cohesion, low-coupling home. Other examples: Mapper, Logger, EventPublisher, CacheManager. All are pure fabrications — invented for the design, not for the domain.
| GRASP Pattern | Corresponding GoF Pattern(s) | What it does in practice |
|---|---|---|
| Information Expert | — (principle, not a pattern) | Method lives in the class with the data |
| Creator | Factory Method, Abstract Factory | Object creation close to usage context |
| Controller | Facade, Command | Single entry point for system events |
| Low Coupling | Facade, Mediator, Adapter | Reduces dependency surface between modules |
| High Cohesion | — (principle, not a pattern) | Related behavior in one class |
| Polymorphism | Strategy, State, Command | Type-based dispatch instead of conditionals |
| Pure Fabrication | Repository, Mapper, Logger | Invented class for infrastructure behavior |
| Indirection | Adapter, Proxy, Decorator, Bridge | Intermediate object absorbs coupling |
| Protected Variations | All patterns — this is the root | Stable interface around volatile change point |