Moving Features Between Objects — Junior Level¶
Source: refactoring.guru/refactoring/techniques/moving-features-between-objects
Table of Contents¶
- What this category is about
- Real-world analogy
- The 8 techniques at a glance
- Move Method
- Move Field
- Extract Class
- Inline Class
- Hide Delegate
- Remove Middle Man
- Introduce Foreign Method
- Introduce Local Extension
- How they relate
- Mini Glossary
- Review questions
What this category is about¶
Moving Features Between Objects answers a single question: "This responsibility — does it live on the right class?"
Where Composing Methods restructures the inside of a method, Moving Features restructures placement: which class owns which method, which field belongs where, when one class is doing the work of two, and when a chain of delegates should be flattened.
The smells these techniques cure are about coupling and cohesion:
- Feature Envy — a method uses another class's data more than its own → Move Method.
- Inappropriate Intimacy — two classes know each other's internals → Move Method / Move Field / Hide Delegate.
- Large Class — a class has too many jobs → Extract Class.
- Lazy Class — a class has nothing meaningful to do → Inline Class.
- Message Chains —
a.getB().getC().doIt()→ Hide Delegate. - Middle Man — a class only forwards calls → Remove Middle Man.
Key idea: placement matters as much as implementation. A correct method on the wrong class is technical debt.
Real-world analogy¶
A reorganization at a small company¶
You're at a 30-person startup. The marketing team handles its own invoicing. The sales team handles its own legal review. The CTO debugs production directly.
This worked when there were 5 people. At 30, things break:
- Marketing's invoicing is inconsistent because they're not accountants.
- Sales' legal review misses things a real lawyer would catch.
- The CTO is a bottleneck.
The CEO moves features between teams: - Invoicing → Finance. - Legal review → Legal. - Production debugging → SRE.
Same total amount of work — better placement. Each team now does what it's best at.
That's exactly what Move Method, Move Field, and Extract Class do at the code level.
The 8 techniques at a glance¶
| Technique | What it does | Inverse |
|---|---|---|
| Move Method | A method belongs on a different class | (Move Method back) |
| Move Field | A field belongs on a different class | (Move Field back) |
| Extract Class | One class doing two jobs → split | Inline Class |
| Inline Class | A class with no real job → fold into another | Extract Class |
| Hide Delegate | Replace a.getB().doIt() with a.doIt() | Remove Middle Man |
| Remove Middle Man | Class only forwards → expose the delegate | Hide Delegate |
| Introduce Foreign Method | Add a wrapper method when you can't modify the class | (delete the wrapper) |
| Introduce Local Extension | Subclass / wrapper holding many foreign methods | (back to wrappers) |
Move Method¶
What it does¶
A method on class A reads/calls more from class B than from A itself. The method belongs on B.
Symptoms¶
- Method body has more
b.x,b.y,b.foo()thanthis.something. - Method takes a
Bas parameter and barely usesthis. - A change in B forces a change in this method.
Before¶
class Account {
private AccountType type;
private double daysOverdrawn;
double overdraftCharge() {
if (type.isPremium()) {
double result = 10;
if (daysOverdrawn > 7) result += (daysOverdrawn - 7) * 0.85;
return result;
}
return daysOverdrawn * 1.75;
}
}
The body asks type.isPremium() and applies a different formula based on it. The decision is really the AccountType's.
After¶
class AccountType {
private boolean isPremium;
public boolean isPremium() { return isPremium; }
double overdraftCharge(double daysOverdrawn) {
if (isPremium) {
double result = 10;
if (daysOverdrawn > 7) result += (daysOverdrawn - 7) * 0.85;
return result;
}
return daysOverdrawn * 1.75;
}
}
class Account {
private AccountType type;
private double daysOverdrawn;
double overdraftCharge() {
return type.overdraftCharge(daysOverdrawn);
}
}
Mechanics (Fowler)¶
- Examine all features used by the method in the source class. Consider moving them too.
- Check sub- and super-classes for other declarations of the method.
- Declare the method in the target class. Copy the body.
- Adjust the method to its new home (rename references).
- Decide how to reference the target object from the source.
- Turn the source method into a delegating call (or remove it).
- Test.
When NOT to move¶
- The method is on an aggregate root that orchestrates several collaborators — moving fragments to each leaf scatters logic.
- The "envious" reads are coincidental (e.g., logging — every method touches the logger).
- The target class is a value object you don't want to grow behavior.
Move Field¶
What it does¶
A field that lives on class A is read or written more by class B. Move it.
Before¶
class Account {
private double interestRate; // ❌ but the formula is on AccountType
private AccountType type;
private double balance;
double interestForAmount(double amount, int days) {
return interestRate * amount * days / 365;
}
}
If interestRate is determined by the type, it belongs on AccountType.
After¶
class AccountType {
private double interestRate;
public double interestRate() { return interestRate; }
}
class Account {
private AccountType type;
private double balance;
double interestForAmount(double amount, int days) {
return type.interestRate() * amount * days / 365;
}
}
When NOT to move¶
- The field genuinely instance-varies (each Account has its own custom rate). Then it stays.
- Moving creates a circular reference (A holds B, B's field needs to refer back to A).
Extract Class¶
What it does¶
One class is doing two distinct jobs. Split it: one becomes two classes; the original delegates the second job to the new one.
Before¶
class Person {
private String name;
private String officeAreaCode;
private String officeNumber;
public String name() { return name; }
public String telephoneNumber() { return "(" + officeAreaCode + ") " + officeNumber; }
public String getOfficeAreaCode() { return officeAreaCode; }
public void setOfficeAreaCode(String code) { officeAreaCode = code; }
public String getOfficeNumber() { return officeNumber; }
public void setOfficeNumber(String n) { officeNumber = n; }
}
A Person is acting as both a person and a phone number. Two responsibilities.
After¶
class TelephoneNumber {
private String areaCode;
private String number;
public String getAreaCode() { return areaCode; }
public void setAreaCode(String c) { areaCode = c; }
public String getNumber() { return number; }
public void setNumber(String n) { number = n; }
public String formatted() { return "(" + areaCode + ") " + number; }
}
class Person {
private String name;
private TelephoneNumber officePhone = new TelephoneNumber();
public String name() { return name; }
public String telephoneNumber() { return officePhone.formatted(); }
public TelephoneNumber officePhone() { return officePhone; }
}
Mechanics¶
- Decide how to split the responsibilities.
- Create a new class.
- Make a link from the source to the new class (one field).
- Use Move Field on each field that belongs on the new class.
- Use Move Method on each method.
- Review the source class — is its remaining surface coherent?
- Decide whether to expose the new class publicly or keep it as an implementation detail.
When to apply¶
- The class has a "phone number" subset of fields — they cluster.
- Some methods only touch field-cluster A; others only touch B.
- The class name is too vague to capture all its jobs (
OrderManagerdoing 7 things).
See also¶
Large Class, Data Clumps, Divergent Change.
Inline Class¶
What it does¶
The reverse of Extract Class. A class isn't doing enough to justify its existence. Fold its responsibilities back into another class and delete it.
Before¶
class TelephoneNumber {
private String number;
public String getNumber() { return number; }
public void setNumber(String n) { number = n; }
}
class Person {
private TelephoneNumber phone = new TelephoneNumber();
public String getNumber() { return phone.getNumber(); }
public void setNumber(String n) { phone.setNumber(n); }
}
TelephoneNumber adds nothing — it just holds one string with getter/setter.
After¶
class Person {
private String phoneNumber;
public String getPhoneNumber() { return phoneNumber; }
public void setPhoneNumber(String n) { phoneNumber = n; }
}
When to apply¶
- A "value object" that doesn't carry meaning — just one primitive in a wrapper.
- A class whose every method delegates to a field (could indicate Middle Man).
- A class created speculatively that turned out to be unused.
When NOT¶
- The class will likely grow (planned feature, polymorphism point).
- The class encapsulates an invariant (e.g.,
Emailvalidates format on set — don't inline that intoPerson.email).
Hide Delegate¶
What it does¶
Hide an intermediate hop in a call chain.
Before¶
class Person {
private Department department;
public Department getDepartment() { return department; }
}
class Department {
private Person manager;
public Person getManager() { return manager; }
}
// Caller:
Person manager = john.getDepartment().getManager();
The caller knows about Department. If you remove or rename Department, every caller of getDepartment().getManager() must change.
After¶
class Person {
private Department department;
public Person manager() {
return department.getManager();
}
}
// Caller:
Person manager = john.manager();
Why¶
- The caller doesn't know about
Department— coupling reduced. - Renaming or refactoring
Departmentonly affectsPerson. - See Demeter's Law: "talk to your direct neighbors, not friends of friends."
Mechanics¶
- For each method on the delegate that callers use, add a delegating method on the source class.
- Update callers to use the source's new method.
- If no callers reach the delegate directly anymore, hide the delegate (remove
getDepartmentif possible).
Trade-off¶
If you hide every delegate method, you'll grow Person's API. There's a balance — see Remove Middle Man for the opposite swing.
Remove Middle Man¶
What it does¶
The opposite of Hide Delegate. A class has so many delegating methods that it's adding no value — expose the delegate directly.
Before¶
class Person {
private Department department;
public Person manager() { return department.getManager(); }
public String departmentName() { return department.getName(); }
public int departmentSize() { return department.getSize(); }
public List<Project> departmentProjects() { return department.getProjects(); }
// ... 15 more delegating methods ...
}
After¶
class Person {
private Department department;
public Department department() { return department; }
}
// Caller:
john.department().manager();
john.department().getName();
When to swing this way¶
- The delegate has 5+ methods that callers want — and the wrapper class is just forwarding.
- The wrapper isn't preserving any invariant or hiding anything meaningful.
See also¶
Introduce Foreign Method¶
What it does¶
You need a method on a class you can't modify (third-party library, JDK class). Write a "foreign" method elsewhere — typically in your own utility class — that takes the foreign type as a parameter.
Before¶
After¶
class DateUtils {
static Date nextDay(Date date) {
return new Date(date.getYear(), date.getMonth(), date.getDate() + 1);
}
}
// Caller:
Date newStart = DateUtils.nextDay(previousEnd);
Notes¶
- Mark the foreign method clearly (Fowler's tip: add a comment
// foreign method: Date). - This is a stop-gap. If you accumulate many foreign methods on the same type, see Introduce Local Extension.
Introduce Local Extension¶
What it does¶
Many foreign methods on the same type? Promote them into a single class — either a subclass or a wrapper — that you can grow.
Subclass form (when the foreign type is non-final)¶
class MfDate extends Date {
public MfDate(Date d) { super(d.getTime()); }
public Date nextDay() { return new MfDate(new Date(getTime() + 86_400_000L)); }
public Date previousDay() { return new MfDate(new Date(getTime() - 86_400_000L)); }
public boolean isBetween(Date a, Date b) { return after(a) && before(b); }
}
Wrapper form (when the foreign type is final or you prefer composition)¶
class MfDate {
private final Date original;
public MfDate(Date d) { this.original = d; }
public Date nextDay() { return new Date(original.getTime() + 86_400_000L); }
public Date asDate() { return original; }
// ... more methods ...
}
Modern alternative¶
Java 8+ has LocalDate, LocalDateTime, Instant — modernizing the type often beats wrapping the legacy Date. Use Local Extension when you can't move off the legacy type yet.
In Kotlin / C# / Swift, extension functions make this nearly free:
In Python, you'd use a free function or subclass:
In Go, you must use a wrapper or function on a named type:
type MfDate time.Time
func (d MfDate) NextDay() MfDate { return MfDate(time.Time(d).AddDate(0, 0, 1)) }
How they relate¶
Large Class
│
▼
Extract Class
│
┌────────────┴───────────┐
▼ ▼
Move Field Move Method
▼ │
new class grows ◄─────────────┘
│
▼
Lazy Class? (no work)
│
▼
Inline Class
Message Chains (a.getB().doIt())
│
▼
Hide Delegate
│
▼
too many delegated methods?
│
▼
Remove Middle Man
The pairs (Hide Delegate ↔ Remove Middle Man) and (Extract Class ↔ Inline Class) swing in opposite directions. You choose based on the current state and the direction you want.
Mini Glossary¶
- Delegate — an object held by another (
Person.department) that handles part of the holder's work. - Middle Man — a class whose methods only forward to a delegate.
- Foreign method — a method written outside the class it logically extends, because you can't modify the original class.
- Local extension — a subclass or wrapper holding multiple foreign methods.
- Aggregate root (DDD) — the entry point to a cluster of related objects; outsiders talk only to the root.
Review questions¶
- What's the difference between Move Method and Extract Method?
- When does Feature Envy demand Move Method?
- What's the relationship between Extract Class and the Single Responsibility Principle?
- When is Inline Class the right move?
- What does Hide Delegate do, and which smell does it cure?
- When should you Remove Middle Man instead of Hide Delegate?
- What's a "foreign method"?
- When would you use Introduce Local Extension over a single foreign method?
- How does Kotlin's extension function relate to Foreign Method / Local Extension?
- What's the trade-off between exposing a delegate vs. forwarding every method?
Next¶
- middle.md — real-world triggers, trade-offs.
- senior.md — at architecture scale.
- professional.md — runtime cost.
- Practice: tasks.md, find-bug.md, optimize.md, interview.md.