Introduce Foreign Method: The Stapler You Keep in Your Own Bag
Learn the Introduce Foreign Method refactoring with a story about a school photocopy machine that has no stapler. When a class you cannot modify is missing a method, write that method in your own class with the foreign object as a parameter. TypeScript and modern C# extension-method examples included.
📎 Arjun and the photocopy machine with no stapler
Arjun is in class 10, and this week he is the unofficial photocopy boy for his project group. The school has one photocopy machine, near the office, guarded by Naik uncle the office clerk. The machine copies beautifully — fast, clear, both sides. But it has one missing feature: no stapler. Every time Arjun photocopies his group's twelve project pages, he gets loose sheets, and the corridor breeze near the office is famous. Last Tuesday, page 7 of the Science project flew into the gents toilet. Nobody went to retrieve it.
Can Arjun fix the machine? No chance. It belongs to the school. It is sealed, under an annual maintenance contract, and Naik uncle will not let anyone near it with a screwdriver. The machine is, in one word, not Arjun's to modify.
So what does Arjun do? After the page 7 incident, he starts keeping a small stapler in his own bag. Photocopy, collect the sheets, thak — stapled, sorted, safe. The stapler is not part of the machine. It travels with Arjun. It does the one job the machine should have done but cannot.
Notice three things about the bag stapler:
- It exists because the machine is missing a feature Arjun needs often. One-time need? He would have borrowed a clip.
- It works on the machine's output — the public thing he receives — not on the machine's insides. The stapler never opens the machine's panels.
- It is a small, single-purpose fix. Arjun did not build a whole new machine. He bought a five-rupee stapler.
Programming has the exact same situation. You use classes you cannot edit every day: Date from the standard library, an HttpResponse from a framework, a generated API client. Sometimes such a class is missing one small method you need again and again. You cannot add it to the class. So you keep the method in your own bag — your own class — and pass the foreign object to it.
That little bag-stapler method is called a foreign method, and writing it deliberately, with a name and a single definition, is the Introduce Foreign Method refactoring.
🔍 What is Introduce Foreign Method?
Introduce Foreign Method comes from Martin Fowler's Refactoring. The situation: a server class you are using needs an extra method, but you cannot modify the class. The recipe: create a method in your client class, with an instance of the foreign class as its first parameter.
Fowler's own example is dates. Code keeps computing "the next day" from a date object the team does not own:
// Scattered across the codebase, again and again:
const dueDate = new Date(
invoice.getFullYear(), invoice.getMonth(), invoice.getDate() + 1
);
// ...somewhere else, the same dance:
const reminderDate = new Date(
start.getFullYear(), start.getMonth(), start.getDate() + 1
);The idea "next day" clearly belongs on the date class. But the date class is foreign. So we park the method in our own class and label it honestly:
class InvoiceScheduler {
// FOREIGN METHOD: belongs on Date; parked here because we cannot modify Date.
private static nextDay(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
}
dueDate(invoiceDate: Date): Date {
return InvoiceScheduler.nextDay(invoiceDate);
}
reminderDate(start: Date): Date {
return InvoiceScheduler.nextDay(start);
}
}Three wins, instantly. The operation has a name (nextDay — intent, not arithmetic). It has one definition (a bug fix happens in one place). And the comment records a debt: this code belongs elsewhere; if the foreign class ever opens up, move it home.
Always write the marker comment — FOREIGN METHOD: belongs on X. It does two jobs: it tells readers why a date calculation is sitting inside an invoice class, and it leaves a searchable breadcrumb. One day you can grep for FOREIGN METHOD and see exactly which library gaps your codebase has been patching.
College corner: Why insist that the foreign object be the first parameter? It is a convention with a payoff. Reading nextDay(date) with the receiver first mimics the method call the foreign class should have offered — date.nextDay() — so the day the class opens up (or the day you switch to a language with extension methods), the migration is mechanical. C# made this convention into syntax: an extension method is literally a static method whose first parameter carries the this modifier, and the compiler rewrites date.NextDay() into the static call DateTimeExtensions.NextDay(date) at compile time. Kotlin extension functions compile the same way — a static function whose receiver becomes parameter zero. The 1999 refactoring and the modern language feature are one idea separated by syntax.
🚦 When do we need it?
Reach for Introduce Foreign Method when all three of these are true:
- You keep needing the same small operation on objects of one type — the duplication has appeared at two, three, five call sites. Repeated inline snippets are the Duplicate Code smell wearing a disguise.
- The operation's natural home is a class you cannot modify — a standard library type, a sealed framework class, generated code, or a third-party package you must not fork.
- You only need one or two such operations. This refactoring is the five-rupee stapler, not a workshop.
When does it become the wrong tool?
| Situation | Better tool |
|---|---|
| You actually own the class | Move Method — put it where it belongs |
| Helpers for the same type keep multiplying | Introduce Local Extension |
| The operation needs the foreign object's private state | No client-side fix exists — petition the library |
| The snippet appears exactly once | Maybe nothing — one inline use is not yet a pattern |
A connecting thought: this refactoring belongs to the same family of "respecting boundaries" as the coupling refactorings. Where Message Chains and Middle Man are about who talks to whom, Introduce Foreign Method is about where behavior may live when the natural home is locked. All of them are answers to one question: how do we keep knowledge in sensible places?
And why does the duplication matter so much? Because copies drift. Watch what usually happens to an inline snippet as a codebase grows:
👀 Before and after at a glance
// ---------- BEFORE: nameless arithmetic, copy-pasted ----------
function billingReport(invoiceDate: Date): string {
const due = new Date(
invoiceDate.getFullYear(), invoiceDate.getMonth(), invoiceDate.getDate() + 1
);
return `Pay by ${due.toDateString()}`;
}
function courierLabel(dispatchDate: Date): string {
const arrives = new Date(
dispatchDate.getFullYear(), dispatchDate.getMonth(), dispatchDate.getDate() + 1
);
return `Arrives ${arrives.toDateString()}`;
}// ---------- AFTER: one named foreign method ----------
// FOREIGN METHOD: belongs on Date; we cannot modify Date.
function nextDay(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
}
function billingReport(invoiceDate: Date): string {
return `Pay by ${nextDay(invoiceDate).toDateString()}`;
}
function courierLabel(dispatchDate: Date): string {
return `Arrives ${nextDay(dispatchDate).toDateString()}`;
}The foreign class did not change — it cannot change. What changed is our side of the boundary: the missing concept now has exactly one definition.
🪜 Step-by-step, the safe way
Step 1 — Catch the duplication. Find every place that performs the operation inline. Search for the distinctive part — here, getDate() + 1. List the call sites.
Step 2 — Write the foreign method next to its first user. In the client class that needs it most, add a method whose first parameter is the foreign object. Copy the snippet's logic into it, expressed through the parameter:
class BillingService {
// FOREIGN METHOD: belongs on Date.
private static nextDay(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
}
// ...nothing else changed yet
}Compile and test. Green — the method exists but nobody calls it yet.
Step 3 — Replace one call site. Swap the first inline snippet for a call to nextDay(...). Run the tests. The behavior must be identical — this is a pure rename-and-centralize move.
Step 4 — Replace the remaining call sites, one at a time. Test after each. If a snippet differs slightly from the others (say, + 2 instead of + 1), stop — that is a different operation needing its own name (dayAfterTomorrow? a parameter?). Do not force different logic into one method just to reduce count.
Step 5 — Add the marker comment. "FOREIGN METHOD: belongs on Date." Future readers and future archaeologists will thank you.
Step 6 — Watch the count. One foreign method: fine. Two: fine. When the third and fourth appear for the same foreign type — especially in different client classes — schedule the upgrade to Introduce Local Extension.
This life cycle — from scattered copies, to a named stapler, to the moment the staplers outgrow the bag — is the story of every locked type in a long-lived codebase:
The most common bug in this refactoring is a subtle behavioral difference between the copies. One call site rolls over month-end correctly, another does not; one treats time zones, another ignores them. Before unifying, write a quick test for each call site's CURRENT behavior. If two copies genuinely behave differently, unifying them silently changes behavior — and that is not refactoring any more, that is editing with your eyes closed.
💳 A bigger real-life example
Your team consumes a third-party payments SDK. It returns PaymentReceipt objects — generated code, regenerated on every SDK update, absolutely not editable:
// Generated by the SDK. DO NOT EDIT.
class PaymentReceipt {
constructor(
public readonly amountPaise: number, // amount in paise!
public readonly status: "OK" | "PENDING" | "FAILED",
public readonly upiRef: string
) {}
}Everyone needs the amount in rupees, formatted Indian-style. So everyone improvises:
// In the orders module:
const label1 = `₹${(receipt.amountPaise / 100).toFixed(2)}`;
// In the refunds module — oops, integer division bug:
const label2 = `₹${Math.floor(receipt.amountPaise / 100)}`;
// In the email module — forgot the symbol:
const label3 = `Rs ${(receipt.amountPaise / 100).toFixed(2)}`;Three copies, three slightly different behaviors, one of them a genuine bug (the refund email shows ₹4 for ₹4.99!). Where do bugs like this come from in code that handles a locked type? Mostly from exactly this kind of re-implementation:
Introduce a foreign method in the shared payments client:
class PaymentsGateway {
// FOREIGN METHOD: belongs on PaymentReceipt (generated SDK code).
static amountInRupees(receipt: PaymentReceipt): string {
return `₹${(receipt.amountPaise / 100).toFixed(2)}`;
}
// FOREIGN METHOD: belongs on PaymentReceipt.
static isSettled(receipt: PaymentReceipt): boolean {
return receipt.status === "OK";
}
}
// All three modules now:
const label = PaymentsGateway.amountInRupees(receipt);The bug gets fixed in one line, in one file, for all three modules at once. And notice the early-warning sign: we are already at two foreign methods for PaymentReceipt. One more — say maskedUpiRef() — and the honest move is to gather them into a proper local extension. The stapler-in-the-bag approach is wonderful precisely because we stay alert to when we have outgrown it.
💻 The same refactoring in C#
Here is where the story gets exciting. C# took the foreign-method idea and built it into the language: extension methods. An extension method is a static method whose first parameter carries the this modifier — and the compiler then lets you call it as if it lived on the foreign type.
The classic foreign method, C#-style:
public static class DateTimeExtensions
{
// The foreign method, native edition:
public static DateTime NextDay(this DateTime date) => date.AddDays(1);
public static bool IsWeekend(this DateTime date) =>
date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}
// Call sites read as if DateTime always had these:
var due = invoiceDate.NextDay();
if (due.IsWeekend()) due = due.NextDay().NextDay();Compare with Fowler's original 1999 pattern — nextDay(previousEnd) as a plain static helper. The C# version is the same mechanics (a static method receiving the foreign object first), but the call site flips around: invoiceDate.NextDay() instead of NextDay(invoiceDate). The method reads like it belongs to the type, even though the type never changed. Kotlin extension functions (fun LocalDate.nextDay() = plusDays(1)) and Swift extensions do the same trick.
And the story keeps evolving: C# 14 (with .NET 10) introduced extension members — extension blocks that allow not just methods but extension properties and even static extension members:
public static class DateTimeExtensions
{
extension(DateTime date)
{
public DateTime NextDay => date.AddDays(1); // extension property!
public bool IsWeekend =>
date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}
}
// Now even property-style access works:
var due = invoiceDate.NextDay;Two honest limits remain, exactly as in Fowler's original: extension members see only the public surface of the type, and discoverability depends on having the right namespace imported. The language polished the syntax; the design idea — and its boundaries — are pure Introduce Foreign Method.
College corner: A subtle point about C#/Kotlin extension resolution that trips up even final-year students: extension methods are resolved statically, at compile time, based on the declared type of the receiver and the imported namespaces. They do not participate in virtual dispatch — if a real instance method with the same signature exists, the instance method always wins, and an extension on a base type will not specialise for a derived runtime type. This is exactly the limitation of the foreign method too: it is bolted on from outside, so it cannot override, cannot see protected state, and cannot be polymorphic. Knowing this tells you when the technique runs out of road — and when you need a wrapper (a true local extension) instead.
🐍 And once in Python
Python's culture is friendly to foreign methods: a plain module-level function taking the foreign object first is idiomatic, searchable, and testable.
# FOREIGN METHOD: belongs on datetime.date; we cannot modify the stdlib.
def next_day(d: date) -> date:
return d + timedelta(days=1)
# Call sites:
due = next_day(invoice_date)
reminder = next_day(due)Python can monkey-patch methods onto many classes at runtime — but resist that for foreign methods. A patched method is invisible to readers, surprises every import order, and breaks on built-in types like datetime.date anyway (C-implemented classes refuse new attributes). The honest module function with the receiver first is the Pythonic stapler.
📈 Stapler or cabin? Read the dial
The decision between this refactoring and its bigger sibling is a count-and-cohesion judgement. Plot your situation:
Like Hide Delegate and Remove Middle Man, this refactoring has a sibling on the other side of a dial — not an opposite, but an escalation. The question the dial answers: how much machinery should you build for a class you cannot edit?
- One or two missing operations → Introduce Foreign Method. A stapler in the bag. Cheap, fast, honest.
- A growing family of missing operations → Introduce Local Extension. Build a proper attached cabin: one wrapper, subclass, or extension class holding the whole family.
Under-build and you scatter helpers everywhere; over-build and you maintain a wrapper class for one tiny method. Start light. Upgrade when the count tells you to.
✅ Benefits and risks
| Benefit | Why it matters |
|---|---|
| One definition instead of scattered copies | Bug fixes and rule changes happen in one place |
| The operation gets a name | nextDay(d) states intent; inline arithmetic states nothing |
| Extremely lightweight | No new type, no inheritance, no wrapper — just a method |
| Documents a real design gap | The marker comment records what the foreign class is missing |
| Risk | How to handle it |
|---|---|
| Method lives in the "wrong" class | Accept it as a deliberate, commented compromise |
| Teammates may not find it and re-duplicate | Put it in a predictable shared spot; in C#/Kotlin, use real extension methods so the IDE surfaces it |
| Helpers multiply and scatter | Two is company, three is a crowd — graduate to Introduce Local Extension |
| Only public interface available | If private state is needed, no client-side fix exists; petition the library or wrap differently |
🧪 Which smells does it cure?
| Smell | How Introduce Foreign Method helps |
|---|---|
| Duplicate Code | Collapses repeated inline snippets on a foreign type into one definition |
| Magic incantations / unnamed concepts | Gives a name to logic that previously read as raw mechanics |
| Shotgun Surgery (for that operation) | A rule change touches one method, not every call site |
| Scattered helpers for one type | ⚠️ Partial — if helpers keep multiplying, you need Introduce Local Extension |
🛠️ IDE support
- Visual Studio / Rider (C#): select the duplicated snippet and use Extract Method; then convert the helper to an extension method (Rider offers a "Convert to extension method" context action when the first parameter qualifies). IntelliSense then surfaces the method on the foreign type for the whole team — solving the discoverability problem foreign methods classically suffer.
- IntelliJ IDEA (Java/Kotlin): in Kotlin, Extract Function followed by "Convert parameter to receiver" turns a helper into an extension function. In Java (no extension methods), the IDE supports the classic pattern via Extract Method into a utility class plus Move Static Method.
- VS Code (TypeScript): Extract to function in module scope creates the helper from a selected snippet; Find All References helps hunt the remaining duplicated copies.
- All IDEs: structural search / regex search is your duplication detector — search for the distinctive fragment (like
getDate() + 1) to be sure you caught every copy.
📦 Quick revision box
+----------------------------------------------------------------+
| INTRODUCE FOREIGN METHOD — CHEAT SHEET |
+----------------------------------------------------------------+
| Situation : class you CANNOT modify is missing a method |
| Move : write the method in YOUR class |
| Signature : foreign object = FIRST parameter |
| Rule : use only the foreign type's PUBLIC interface |
| Mark it : comment "FOREIGN METHOD: belongs on X" |
| Cures : Duplicate Code around library types |
| Limit : 1-2 methods only |
| Outgrown? : 3+ helpers -> Introduce Local Extension |
| Modern form : C# extension methods / Kotlin extension funcs |
| Own the class? Skip all this — use Move Method instead |
+----------------------------------------------------------------+✏️ Practice exercise
Your project uses a third-party SMS gateway. Its response type is generated and locked:
// Generated by the SMS SDK. DO NOT EDIT.
class SmsResult {
constructor(
public readonly phone: string, // "+919876543210"
public readonly creditsUsed: number, // 1 credit = ₹0.18
public readonly deliveredAt: Date | null
) {}
}And this code appears, with small variations, in three modules:
// dashboard module:
const cost1 = `₹${(result.creditsUsed * 0.18).toFixed(2)}`;
const masked1 = result.phone.slice(0, 3) + "XXXXX" + result.phone.slice(-2);
// billing module:
const cost2 = `Rs. ${result.creditsUsed * 0.18}`; // formatting differs!
const delivered = result.deliveredAt !== null;
// audit module:
const masked2 = result.phone.substring(0, 3) + "*****" + result.phone.slice(-2);Your tasks:
- Identify the distinct missing operations on
SmsResult. (Hint: there are three: cost in rupees, masked phone, was-delivered.) - Notice that the two masking copies and the two cost copies behave differently. Decide which behavior is correct, and write a tiny test for it before unifying.
- Write the foreign methods in a
SmsReportsclient class, foreign object as first parameter, marker comments included. Replace all call sites one by one, testing between each. - Sketch Figure 6 for this codebase: which state is
SmsResultin right now, and which arrow are you about to follow? - Count your foreign methods for
SmsResult. You have three. According to the dial, what refactoring should you now be considering — and what would you name the new type?
If your answer to task 5 was "Introduce Local Extension, maybe a SmsReceipt wrapper or SmsResultExtensions class," turn the page — that is exactly the next post. Arjun, for his part, eventually stopped carrying one stapler, one punch, one folder and one roll of tape in his bag. He bought a small project kit box. That kit box is where our story goes next.
Frequently asked questions
- What is the Introduce Foreign Method refactoring?
- When a class you cannot modify (a library, framework, or sealed type) is missing a method you keep needing, you write that method inside your own client class, taking the foreign object as a parameter. The classic example is a nextDay(date) helper for a date class you do not own. The duplicated snippet gets one name and one definition.
- Why is it called a 'foreign' method?
- Because the method logically belongs to a foreign class — one outside your control — but physically lives in your own code. It is a guest worker: it operates entirely on the foreign object's public interface while sitting in your client class. A comment usually marks it as a stand-in for a method the foreign class should have had.
- When should I use Introduce Local Extension instead?
- Fowler's rule of thumb: one or two missing methods, use Introduce Foreign Method; more than that, graduate to Introduce Local Extension, which gathers all the helpers into one dedicated wrapper, subclass, or extension class. Scattered foreign methods for the same type are a sign you have outgrown the lightweight fix.
- Are C# extension methods the same as foreign methods?
- They are the modern, language-supported evolution of the same idea. A C# extension method is a static method whose first parameter has the 'this' modifier, letting you call it as if it were on the foreign type: date.NextDay() instead of NextDay(date). Kotlin extension functions and Swift extensions do the same. The mechanics differ, but the purpose — adding behavior to a type you cannot edit — is identical.
- What are the limitations of a foreign method?
- It can only use the foreign object's public interface — no private state. It lives in the 'wrong' class, so other teams may not find it and may re-duplicate the logic. And if you actually own the class, foreign methods are the wrong tool: just add the method where it belongs, or use Move Method.
Further reading
Related Lessons
Introduce Local Extension: Build a Cabin Next to the Rented Shop
Learn the Introduce Local Extension refactoring with a story about building an attached cabin beside a rented shop you cannot modify. When a locked class is missing many methods, gather them into one extension type — a subclass, a wrapper, or a modern C#/Kotlin extension class. Full TypeScript and C# walkthrough.
Duplicate Code: Writing the Same Address on 50 Wedding Cards
Learn the Duplicate Code smell with a wedding card story. Understand DRY, the Rule of Three, and how Extract Method removes dangerous copy-paste code.
Extract Method: Turn One Giant Function into Small Named Helpers
Learn Extract Method step by step. Pull a messy block out of a long function, give it a clear name, and make your code read like a clean to-do list.
Move Method: Shift Work to the Class Where It Truly Belongs
Learn the Move Method refactoring through a simple school story. Shift a method into the class whose data it uses most so behaviour and data stay together.