Signing you in…

GRASP: Assigning Responsibilities to Objects

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.

ℹ️GRASP patterns are design heuristics, not strict rules. Several patterns work together on the same design decision — for example, a Controller satisfies both the Controller and Creator patterns simultaneously. The value of GRASP is not in applying each pattern in isolation, but in having shared vocabulary for responsibility assignment discussions.
The Nine GRASP Patterns at a Glance
Click each card for a quick overview — detailed examples follow below
🧠
Information Expert
🏭
Creator
🎮
Controller
🔗
Low Coupling
🎯
High Cohesion
🔀
Polymorphism
⚗️
Pure Fabrication
↩️
Indirection
🛡️
Protected Variations
Information Expert: The Most Used Pattern

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.

Click highlighted lines to see Information Expert applied correctly and violated
java
1
// ── VIOLATION: behavior separated from the data it needs ────────
2
class OrderPricingService {
3
  Money calculateTotal(Order order) {
4
    return order.getItems().stream()                      // reaching into Order
5
      .map(i -> i.getPrice().multiply(i.getQuantity()))   // using Order's data
6
      .reduce(Money.ZERO, Money::add);
7
  }
8
}
9
10
// ── FIX: assign responsibility to the class with the information ─
11
class Order {
12
  private final List<OrderLineItem> items;
13
14
  Money total() {
15
    return items.stream()
16
      .map(OrderLineItem::subtotal)  // delegate to each item's expert
17
      .reduce(Money.ZERO, Money::add);
18
  }
19
}
20
21
class OrderLineItem {
22
  private final Money price;
23
  private final int quantity;
24
  Money subtotal() { return price.multiply(quantity); }  // expert for its own data
25
}
Controller: Separating System Events from Domain Logic

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.

Click highlighted lines to see the Controller pattern and its boundaries
java
1
// ── GRASP Controller: delegates, does not compute ───────────────
2
@RestController
3
class PlaceOrderController {
4
  private final PlaceOrderService service;
5
6
  @PostMapping("/orders")
7
  ResponseEntity<OrderResponse> place(@Valid @RequestBody PlaceOrderRequest req) {
8
    // Controller responsibilities: receive, validate format, map, delegate
9
    PlaceOrderCommand cmd = req.toCommand();     // map input
10
    Order order = service.placeOrder(cmd);       // delegate ALL logic
11
    return ResponseEntity.ok(OrderResponse.from(order)); // map output
12
    // NO business logic here — zero domain rules in the controller
13
  }
14
}
15
16
// ── Same domain, different controller (Kafka consumer) ──────────
17
@KafkaListener(topics = "order-requests")
18
class PlaceOrderEventController {
19
  private final PlaceOrderService service;
20
  void handle(PlaceOrderEvent event) {
21
    service.placeOrder(event.toCommand());  // same service, different entry point
22
  }
23
}
Polymorphism: Replacing Conditionals with Types

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.

Click highlighted lines to see how Polymorphism replaces a type switch
java
1
// ── VIOLATION: type switch that grows with every new discount ────
2
Money applyDiscount(Order order, DiscountType type) {
3
  return switch (type) {
4
    case PERCENTAGE -> order.total().multiply(0.9);
5
    case FIXED      -> order.total().subtract(Money.of(10));
6
    case BOGO       -> order.total().multiply(0.5);  // added later — modified method
7
  };
8
}
9
10
// ── FIX: each type encapsulates its own behavior ─────────────────
11
interface DiscountPolicy { Money apply(Money total); }
12
13
record PercentageDiscount(double rate) implements DiscountPolicy {
14
  public Money apply(Money total) { return total.multiply(1 - rate); }
15
}
16
record FixedDiscount(Money amount) implements DiscountPolicy {
17
  public Money apply(Money total) { return total.subtract(amount); }
18
}
19
record BogoDiscount() implements DiscountPolicy {
20
  public Money apply(Money total) { return total.multiply(0.5); }
21
}
22
23
// Caller — never changes when new discount types are added:
24
Money discounted = policy.apply(order.total());
Protected Variations: The Root of All Patterns

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.

Click each card to see a Protected Variations application in a real system
💳
Volatile: Payment Provider
🗄️
Volatile: Database Technology
📜
Volatile: Business Rules
🌐
Volatile: External API
Pure Fabrication: When Domain Objects Are Not Enough

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 patterns map to GoF design patterns — the connection makes both clearer
GRASP PatternCorresponding GoF Pattern(s)What it does in practice
Information Expert— (principle, not a pattern)Method lives in the class with the data
CreatorFactory Method, Abstract FactoryObject creation close to usage context
ControllerFacade, CommandSingle entry point for system events
Low CouplingFacade, Mediator, AdapterReduces dependency surface between modules
High Cohesion— (principle, not a pattern)Related behavior in one class
PolymorphismStrategy, State, CommandType-based dispatch instead of conditionals
Pure FabricationRepository, Mapper, LoggerInvented class for infrastructure behavior
IndirectionAdapter, Proxy, Decorator, BridgeIntermediate object absorbs coupling
Protected VariationsAll patterns — this is the rootStable interface around volatile change point
Key takeaway: GRASP provides nine named heuristics for the most common responsibility assignment decisions in object-oriented design. Information Expert keeps behavior close to data. Controller separates delivery from domain. Polymorphism replaces conditionals with types. Pure Fabrication gives infrastructure a clean home. Protected Variations is the unifying principle behind every abstraction and every design pattern.
ℹ️What's next: SOLID and GRASP govern how responsibilities are assigned within and between classes. The next lesson steps back further to three cross-cutting heuristics — KISS, DRY, and YAGNI — that govern the overall complexity budget of a design, and explains the Principle of Least Surprise that ties them together.