Refactoring techniqueOrganizing Data
Change Reference to Value
Make a small shared object immutable and compare it by content, so it can be copied freely instead of tracked by identity.
Problem
A small object, such as a currency, a date range or a geo point, is shared by reference and mutated in place. Mutating it through one holder silently changes it for every other holder, and code has to defensively copy it or look it up in a registry.
Solution
Make the object immutable, give it value equality, and replace in-place mutation with methods that return a new instance.
Before
class DateRange {
constructor(public start: Date, public end: Date) {}
extendBy(days: number): void {
this.end = new Date(this.end.getTime() + days * 86_400_000);
}
}
const promoWindow = new DateRange(new Date("2026-11-01"), new Date("2026-11-30"));
const blackFriday = { name: "BF", window: promoWindow };
const cyberMonday = { name: "CM", window: promoWindow };
cyberMonday.window.extendBy(2); // blackFriday's window also movedAfter
class DateRange {
constructor(readonly start: Date, readonly end: Date) {
Object.freeze(this);
}
extendBy(days: number): DateRange {
return new DateRange(this.start, new Date(this.end.getTime() + days * 86_400_000));
}
equals(other: DateRange): boolean {
return (
this.start.getTime() === other.start.getTime() &&
this.end.getTime() === other.end.getTime()
);
}
}
const promoWindow = new DateRange(new Date("2026-11-01"), new Date("2026-11-30"));
const blackFriday = { name: "BF", window: promoWindow };
const cyberMonday = { name: "CM", window: promoWindow.extendBy(2) };Why
- Sharing is safe. Nothing can change the range under another holder.
- No registry or defensive copies needed; equality is by content.
- Values are easier to reason about in concurrent code and to cache.
Steps
- Check the object has no identity that matters: two instances with the same data should be interchangeable.
- Remove setters one at a time. Replace each mutation with construction of a new instance, and reassign at the caller. Run the tests after each.
- Mark fields
readonlyand freeze the instance if your language supports it. - Add
equals(andhashCodein Java-like languages). Replace identity comparisons with it. - Remove any registry or lookup used to share instances.
Watch out
- Fields holding mutable objects defeat immutability.
Dateis mutable in JavaScript;readonlystops reassignment, notrange.start.setDate(...). Copy on the way in or use an immutable date type. - Don't convert real entities (a customer, an account) whose changes must be visible everywhere. Those need identity.
- Frequent changes to a large value object allocate many copies. Usually negligible; measure if it sits in a hot loop.
- The inverse is Change Value to Reference.
Related
- Reduces Inappropriate Intimacy through shared mutable state.
- Inverse: Change Value to Reference.
- Often follows Replace Data Value with Object. Shared immutable values are the basis of Flyweight.
Agent checklist
- The object has no meaningful identity.
- All setters and mutating methods were replaced with methods returning new instances.
- Nested mutable fields are copied or replaced with immutable types.
- Equality is by value and identity comparisons were updated.
- Tests pass.
Treats these smells
See also
- Change Value to ReferenceReplace many identical copies of an entity with one shared instance looked up from a registry, so updates are seen everywhere.
- Replace Data Value with ObjectTurn a primitive field that has grown rules and companions into a small class that owns its data and behavior.
- 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.