# Replace Method with Method Object

> Turn a long method with tangled local variables into its own class, so the locals become fields and the body can be split freely.

- kind: technique
- category: composing-methods
- aliases: Replace Function with Command
- canonical: https://aipatterns.dev/refactoring/techniques/replace-method-with-method-object
- inspired-by: https://refactoring.guru/replace-method-with-method-object

## Problem

A long method uses many local variables that each fragment reads and writes. [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md) would need helpers with six parameters and several return values, so nobody splits it.

## Solution

**Create a class for the method. Make the original object, the parameters and every local variable fields of that class, and move the body into a single `run()` method.** Fragments can now be extracted into private methods that share state through fields instead of parameters.

## Before

```ts
class PayrollService {
  constructor(private taxBands: TaxBand[]) {}

  netPay(employee: Employee, period: PayPeriod): number {
    let gross = employee.hourlyRate * period.regularHours;
    let overtime = 0;
    if (period.overtimeHours > 0) {
      overtime = employee.hourlyRate * 1.5 * period.overtimeHours;
      gross += overtime;
    }
    let taxable = gross - employee.pensionRate * gross;
    let tax = 0;
    for (const band of this.taxBands) {
      const inBand = Math.max(0, Math.min(taxable, band.upTo) - band.from);
      tax += inBand * band.rate;
    }
    const deductions = employee.benefits.reduce((s, b) => s + b.costPerPeriod, 0);
    return gross - tax - employee.pensionRate * gross - deductions;
  }
}
```

## After

```ts
class PayrollService {
  constructor(private taxBands: TaxBand[]) {}

  netPay(employee: Employee, period: PayPeriod): number {
    return new NetPayCalculation(this.taxBands, employee, period).run();
  }
}

class NetPayCalculation {
  private gross = 0;

  constructor(
    private readonly taxBands: TaxBand[],
    private readonly employee: Employee,
    private readonly period: PayPeriod,
  ) {}

  run(): number {
    this.gross = this.regularPay() + this.overtimePay();
    return this.gross - this.incomeTax() - this.pension() - this.benefitDeductions();
  }

  private regularPay() { return this.employee.hourlyRate * this.period.regularHours; }
  private overtimePay() { return this.employee.hourlyRate * 1.5 * this.period.overtimeHours; }
  private pension() { return this.employee.pensionRate * this.gross; }

  private incomeTax(): number {
    const taxable = this.gross - this.pension();
    return this.taxBands.reduce(
      (tax, b) => tax + Math.max(0, Math.min(taxable, b.upTo) - b.from) * b.rate,
      0,
    );
  }

  private benefitDeductions(): number {
    return this.employee.benefits.reduce((s, b) => s + b.costPerPeriod, 0);
  }
}
```

## Why

- Fields replace parameter lists between helpers, so the method splits along natural seams.
- The calculation is testable without the rest of `PayrollService`.
- The service shrinks to an entry point; the algorithm has a home it can grow in.

## Steps

1. Create a new class named after the method's job.
2. Give it a constructor that takes the original object (if the body uses it) and every parameter. Store each as a `readonly` field.
3. Declare a field for each local variable the body uses.
4. Add a `run()` method and copy the body into it. Replace parameter and local references with fields, and calls on `this` with calls on the stored original object.
5. Replace the original method body with `return new X(...).run()`. Run the tests.
6. Now apply [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md) inside the new class, one fragment at a time, running tests after each.

## Watch out

- Don't use this when plain [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md) with [Replace Temp with Query](https://aipatterns.dev/refactoring/techniques/replace-temp-with-query.md) works. A new class is heavier.
- The method object is single-use. Don't cache it or call `run()` twice; mutable fields make a second run unreliable.
- If the original method touched private members of its class, you must expose them or pass them in. Exposing too much creates [Inappropriate Intimacy](https://aipatterns.dev/refactoring/smells/inappropriate-intimacy.md).
- In languages with closures, a nested function sharing locals can achieve the same split with less ceremony.
- To undo, inline `run()` back into the caller and delete the class ([Inline Class](https://aipatterns.dev/refactoring/techniques/inline-class.md)).

## Related

- Fixes [Long Method](https://aipatterns.dev/refactoring/smells/long-method.md) and helps with [Large Class](https://aipatterns.dev/refactoring/smells/large-class.md).
- Structurally the same as [Command](https://aipatterns.dev/design-patterns/command.md), but used for readability rather than queuing or undo.
- Inverse direction: [Inline Class](https://aipatterns.dev/refactoring/techniques/inline-class.md).

## Agent checklist

- [ ] Plain extraction was tried first and failed because of shared locals.
- [ ] The new class receives everything through its constructor and exposes only `run()`.
- [ ] The original method delegates in one line and its signature is unchanged.
- [ ] Extracted helpers inside the new class are small and named.
- [ ] Tests pass after each step.
