Course content
Chain of Responsibility: Build a Purchase Approval Chain
Medium·Tagsdesign-patternschain-of-responsibilitybehaviouralrefactoring
Problem Statement
An approval system needs to route purchase requests through three roles, each authorised up to a different spending limit:
- Manager — handles requests up to $1,000
- Director — handles requests up to $10,000
- VP — handles requests up to $100,000
The chain forwards a request from the lowest authority upward until one approver can sign off. If the amount exceeds even the VP's limit, the chain returns `"REJECTED"`. The starter gives you the abstract `Approver` base class (with `next`, `setNext`, and a `forwardOrReject` helper) plus a `PurchaseRequest` value object. Implement the three concrete approvers:
```java
class Manager extends Approver {
public String approve(PurchaseRequest r);
// amount <= 1_000 → return "Manager"
// otherwise → forwardOrReject(r)
}
class Director extends Approver {
public String approve(PurchaseRequest r);
// amount <= 10_000 → return "Director"
// otherwise → forwardOrReject(r)
}
class Vp extends Approver {
public String approve(PurchaseRequest r);
// amount <= 100_000 → return "VP"
// otherwise → forwardOrReject(r)
}
```
Wiring is fluent: `new Manager().setNext(new Director()).setNext(new Vp());` builds the chain in one expression because `setNext` returns the approver it received as an argument.
The validator runs three checks:
1. each_role_handles_within_limit — a standalone `Manager` (no chain) returns `"Manager"` for $500 and `"REJECTED"` for $5,000 (no one to forward to). Same shape for `Director` (handles $5,000, rejects $50,000 standalone) and `Vp` (handles $50,000).
2. chain_forwards_to_next_handler — full chain Manager → Director → VP routes $500 to Manager, $5,000 to Director, $50,000 to VP. Each approver hands off without short-circuiting incorrectly.
3. chain_exhaustion_and_setnext — a $500,000 request through the full chain returns `"REJECTED"`; `setNext(x)` returns `x` (so chained `setNext` calls compile and work).
The stub approvers throw `UnsupportedOperationException` from every method, so they fail every check until `approve` is implemented on each.
Examples
Example 1
Input
var chain = new Manager(); chain.setNext(new Director()).setNext(new Vp()); chain.approve(new PurchaseRequest(500, "laptop"))Output
"\"Manager\""Why
$500 is within Manager's limit; the chain stops at the first capable approver.
Example 2
Input
chain.approve(new PurchaseRequest(50_000, "servers"))Output
"\"VP\""Why
$50,000 exceeds Manager's $1k and Director's $10k. Both forward; VP handles.
Example 3
Input
chain.approve(new PurchaseRequest(500_000, "office expansion"))Output
"\"REJECTED\""Why
Even VP cannot authorise $500,000. The chain is exhausted and forwardOrReject returns 'REJECTED'.
Constraints
- •Manager, Director, and Vp must each extend Approver.
- •approve(PurchaseRequest) returns 'Manager', 'Director', or 'VP' when within that role's limit, otherwise forwards via forwardOrReject.
- •Manager limit: $1,000. Director limit: $10,000. VP limit: $100,000.
- •Use the inherited forwardOrReject(...) helper rather than calling next.approve(...) directly. The helper handles the null-next case for you.
- •approve must not throw for amounts that exceed every approver's limit — it returns 'REJECTED' instead.
Hints
Stuck? Reveal a nudge toward the right pattern, one step at a time.
Hint 1
Each approve() method is one ternary expression: amount <= LIMIT ? "<role>" : forwardOrReject(request).
Hint 2
Use the inherited forwardOrReject(...) helper rather than referencing `next` directly. The helper handles the null-next case (returns 'REJECTED') so you do not have to.
Hint 3
Manager limit is $1,000. Director is $10,000. VP is $100,000. The order in which the chain is wired determines who tries first; each role's limit is the only thing that determines who succeeds.
Hint 4
The role names returned must be exactly 'Manager', 'Director', 'VP' — capitalisation matters for the validator.