Symptom

A function on class A that reads only data on class B — A.method() reaches through getters on B to compute something B should compute.

Goal

Each function lives where its data lives; coupling between modules drops.

Before the refactoring

class Order {
account;
isVip() {
return this.account.tier === 'gold' && this.account.yearsActive >= 3;
}
}

After the refactoring

class Account {
tier; yearsActive;
isVip() { return this.tier === 'gold' && this.yearsActive >= 3; }
}
class Order { account; }
order.account.isVip();
Example source: Illustrative example written for this site, not a quotation from any source.
Pressure

B's internals leak through public surfaces just to support A's method; refactoring B becomes a coordination across A's call sites.

Tradeoff

Dependencies don't always travel cleanly — circular imports surface at the destination, and readers' 'where does this live' map briefly breaks.

Relief

Modules become more cohesive; tests stay focused; feature-envy patterns disappear.

Trap

Moving every cross-class read mechanically — including functions that legitimately compose data across multiple classes — fan-outs the move and breaks coherent abstractions.