← Chain of responsibility lesson
Lab

Chain of responsibility

Sign in to run your code

Read the brief and the starter here. With a free account you can edit it, run the sample, step through it and check your work, and your progress is kept.

// An expense report, and what one approver decides about it.
type Expense = { id: string; amount: number };
type Decision = 'pass' | 'approved';
type Approver = { name: string; handle: (expense: Expense) => Decision };
type Outcome = { status: string; by: string; checked: string[] };
// One person on the team's roster, as the HR system lists them.
type Member = { name: string; limit: number; away: boolean };

// How an approver decides: approve up to a limit, pass anything larger.
function approveUpTo(expense: Expense, limit: number): Decision {
  return expense.amount <= limit ? 'approved' : 'pass';
}

// Offer the expense to each approver in order; the first decision that is
// not a pass ends it.
function routeExpense(expense: Expense, approvers: Approver[]): Outcome {
  const checked: string[] = [];
  for (const candidate of approvers) {
    const decision = candidate.handle(expense);
    checked.push(candidate.name);
    if (decision !== 'pass') return { status: decision, by: candidate.name, checked };
  }
  return { status: 'unhandled', by: 'nobody', checked };
}

// One line per expense: what happened, and who saw it on the way.
function report(expense: Expense, outcome: Outcome): string {
  const path = outcome.checked.join(' > ') || '-';
  const result =
    outcome.status === 'unhandled' ? 'unhandled' : `${outcome.status} by ${outcome.by}`;
  return `${expense.id}: ${result}; checked ${path}`;
}

// Written for the platform team in March: Sam approves up to 100, Dana up to
// 1,000 and finance up to 10,000. Every team now submits through it.
const approvers: Approver[] = [
  { name: 'Sam', handle: (expense) => approveUpTo(expense, 100) },
  { name: 'Dana', handle: (expense) => approveUpTo(expense, 1000) },
  { name: 'finance', handle: (expense) => approveUpTo(expense, 10000) },
];

// Route one expense report, given the submitting team's roster.
function submit(expense: Expense, roster: Member[]): string {
  return report(expense, routeExpense(expense, approvers));
}