Refactoring techniqueOrganizing Data
Change Value to Reference
Replace many identical copies of an entity with one shared instance looked up from a registry, so updates are seen everywhere.
Problem
Each order holds its own copy of a Customer object built from the same data. When one copy changes (a new credit limit, a renamed company), the others stay stale. The data is really one entity, but the code treats it as many values.
Solution
Keep one instance per identity in a repository or registry, and have every owner hold a reference to that instance instead of creating its own.
Before
class Customer {
constructor(readonly id: string, public creditLimit: number) {}
}
class Order {
readonly customer: Customer;
constructor(customerId: string, creditLimit: number, public total: number) {
this.customer = new Customer(customerId, creditLimit);
}
}
const a = new Order("C-77", 5_000, 1_200);
const b = new Order("C-77", 5_000, 3_900);
a.customer.creditLimit = 10_000; // b still sees 5_000After
class Customer {
constructor(readonly id: string, public creditLimit: number) {}
}
class CustomerRegistry {
private byId = new Map<string, Customer>();
get(id: string, creditLimit: number): Customer {
let c = this.byId.get(id);
if (!c) {
c = new Customer(id, creditLimit);
this.byId.set(id, c);
}
return c;
}
}
class Order {
constructor(readonly customer: Customer, public total: number) {}
}
const customers = new CustomerRegistry();
const a = new Order(customers.get("C-77", 5_000), 1_200);
const b = new Order(customers.get("C-77", 5_000), 3_900);
a.customer.creditLimit = 10_000; // b sees 10_000Why
- One update reaches every holder. No sync code, no stale copies.
- Less memory when many objects share the same entity.
- Identity checks (
a.customer === b.customer) now mean "same customer".
Steps
- Decide where shared instances live: an existing repository, a unit-of-work, or a new registry. Avoid a global singleton if a scoped object will do.
- Add a lookup that returns the existing instance for an id or creates it once.
- Change the owner's constructor to accept a
Customerinstead of building one. Update callers to fetch it from the registry. Run the tests. - Remove the old creation path.
Watch out
- Shared mutable state means any holder can change the entity for everyone. Make sure that is the intended semantics.
- The registry's lifetime matters. A process-wide cache in a server leaks data between requests and tenants; scope it to a request or transaction.
- In distributed systems, a reference is often an id plus a fetch, not a pointer. Don't hold stale shared objects across network boundaries.
- If the object never changes, keep it as a value; sharing gives no benefit.
- The inverse is Change Reference to Value.
Related
- Fixes Duplicate Code in the form of duplicated entity data and the Shotgun Surgery of keeping copies in sync.
- Inverse: Change Reference to Value.
- The registry is often a Factory Method; sharing immutable instances is Flyweight.
Agent checklist
- The object is an entity with identity and changes that all holders must see.
- The registry's scope (request, transaction, process) matches the data's ownership.
- Owners receive instances instead of constructing them.
- No caller still builds its own copy.
- Tests pass, including one that shows an update is visible through every holder.
Treats these smells
See also
- Change Reference to ValueMake a small shared object immutable and compare it by content, so it can be copied freely instead of tracked by identity.
- Replace Data Value with ObjectTurn a primitive field that has grown rules and companions into a small class that owns its data and behavior.
- Factory MethodLet a subclass or a supplied function decide which concrete object to create, so shared workflow code depends only on an interface.
- FlyweightShare one immutable object for state that many objects have in common, and pass the per-object rest in, to cut memory when instances number in the millions.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.