Skip to content
aipatterns.dev

Refactoring techniqueMoving Features between Objects

Introduce Foreign Method

Add a helper in your own code that takes an instance of a class you cannot change and provides the method it lacks.

Problem

You need an operation on a library or framework class, such as Date or a vendor SDK type, but you cannot add methods to it. The same few lines of workaround are copied into several places.

Solution

Write one function in your code that takes the foreign object as its first parameter and does the work. Name it as if it were a method on that class, and mark it as a stand-in for a missing method.

Before

TypeScript
// in the scheduler
const start = new Date(meeting.start);
while (start.getDay() === 0 || start.getDay() === 6) {
  start.setDate(start.getDate() + 1);
}

// in invoicing
const due = new Date(invoice.issuedAt);
due.setDate(due.getDate() + 30);
while (due.getDay() === 0 || due.getDay() === 6) {
  due.setDate(due.getDate() + 1);
}

After

TypeScript
// dates.ts - foreign method: belongs on Date
export function nextBusinessDay(date: Date): Date {
  const result = new Date(date);
  while (result.getDay() === 0 || result.getDay() === 6) {
    result.setDate(result.getDate() + 1);
  }
  return result;
}

// in the scheduler
const start = nextBusinessDay(new Date(meeting.start));

// in invoicing
const due = new Date(invoice.issuedAt);
due.setDate(due.getDate() + 30);
const dueOnBusinessDay = nextBusinessDay(due);

Why

  • The rule exists once. A holiday calendar later changes one function.
  • Call sites read as intent.
  • Returning a new Date removes the mutation hazard of shared instances.

Steps

  1. Identify the repeated workaround and the foreign class it operates on.
  2. Create a function in a shared module of your own. Its first parameter is the foreign object; other inputs follow.
  3. Move the workaround body into it. Avoid mutating the argument; return a new value where you can.
  4. Replace one call site. Run the tests. Replace the rest.
  5. Add a short comment that it is a foreign method, so it can move into the class if you ever own it.

Watch out

  • In TypeScript, don't patch Date.prototype or other built-ins. It leaks to every module and to third-party code. A plain function is safer.
  • If you find yourself writing many foreign methods for the same class, collect them with Introduce Local Extension.
  • Languages with extension methods (C#, Kotlin, Swift) let you write the same helper with method-call syntax. That is the idiomatic form there, not a different refactoring.
  • If you own the class after all, add the method to it with Move Method instead.
  • No inverse; inline the helper back with Inline Method if it proves pointless.

Agent checklist

  • The class really cannot be modified (third-party, built-in or generated).
  • The helper takes the foreign object as its first parameter and does not mutate it.
  • All duplicate workarounds now call the helper.
  • No global prototype was patched.
  • Tests pass.

Treats these smells

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.