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
// 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
// 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
Dateremoves the mutation hazard of shared instances.
Steps
- Identify the repeated workaround and the foreign class it operates on.
- Create a function in a shared module of your own. Its first parameter is the foreign object; other inputs follow.
- Move the workaround body into it. Avoid mutating the argument; return a new value where you can.
- Replace one call site. Run the tests. Replace the rest.
- 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.prototypeor 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.
Related
- Fixes Incomplete Library Class and Duplicate Code.
- Escalate to Introduce Local Extension when helpers multiply.
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
- Introduce Local ExtensionWrap or subclass a class you cannot change in your own class that adds the methods you need.
- Extract MethodMove a coherent fragment of a long function into its own well-named function and call it from the original place.
- Move MethodMove a method to the class whose data it uses most, and leave a delegating call or update the callers.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.