Card 04/ 08

ExampleDifficulty: Intermediate2 min

Chain of Responsibility Example: Approving an Expense by Amount

A team lead approves up to 500, a manager up to 5,000, and a director anything above.

An approval process with spending limits is a natural Chain of Responsibility, and this example maps the template onto it. A team lead may approve an expense up to 500, a manager up to 5,000, and a director any amount. An employee submits every expense to their team lead, and it climbs only as far as it needs to.

The template's parts in the expense example
TemplateExpense example
HandlerApprover, with a title, a limit and the next approver
ConcreteHandlerThree Approver objects with different limits
The handling testamount <= self.limit
ClientThe loop that sends each amount to team_lead
py
class Approver:
    def __init__(self, title: str, limit: float, next_approver: "Approver | None" = None) -> None:
        self.title = title
        self.limit = limit
        self.next_approver = next_approver

    def approve(self, amount: float) -> str:
        if amount <= self.limit:
            return f"{amount:,.0f} approved by the {self.title}"
        if self.next_approver is None:
            return f"{amount:,.0f} refused: above every limit"
        print(f"  {self.title} passes {amount:,.0f} up")
        return self.next_approver.approve(amount)


director = Approver("director", float("inf"))
manager = Approver("manager", 5_000, director)
team_lead = Approver("team lead", 500, manager)

for amount in (120, 3_200, 48_000):
    print(team_lead.approve(amount))
OutputRan with python:3.12-slim (Python 3.12.13)
120 approved by the team lead
  team lead passes 3,200 up
3,200 approved by the manager
  team lead passes 48,000 up
  manager passes 48,000 up
48,000 approved by the director
One class, configured three times, makes the chain.

The 120 expense stops at the team lead, 3,200 climbs one level, and 48,000 climbs two, with each pass printed. The director's limit is infinity, so the chain always ends in a handler that accepts the request. The next_approver is None branch still refuses explicitly, in case a chain is built without a director.

The handlers here are one class with different data, not three classes. That is common when the handlers differ only in a value. When they differ in how they decide, such as one checking an amount and another checking a category, each becomes its own class.