Skip to main content

Encapsulate Record and Replace Primitive with Object

What This Concept Is

Two moves that give shapeless data a home and behavior.

Encapsulate Record: a bare record / map / hash / plain object is being read and mutated from many places. Wrap it in a class. Put accessors on the class. Migrate callers to go through the class.

Replace Primitive with Object: a field currently a string or number is starting to attract logic (format, validate, compare, derive). Create a small class for it. Its constructor takes the primitive; its methods replace the scattered format/validate logic.

Why It Matters Here

Fowler calls Replace Primitive with Object one of the most valuable refactorings in the toolkit -- and the most counterintuitive for new programmers. The reason: a priority string becomes ten scattered comparisons ("high", "rush", o.priority === "critical"). A Priority class collapses them into priority.higherThan(other).

Encapsulate Record is the prerequisite for many later refactors: once a record has a class, you can rename fields, change storage, or replace it with a different structure without changing callers.

Concrete Example

Encapsulate Record:

// Before
const organization = { name: "Acme Gooseberries", country: "GB" };
result += `<h1>${organization.name}</h1>`;
organization.name = newName;

// After
class Organization {
constructor(data) { this._name = data.name; this._country = data.country; }
get name() { return this._name; }
set name(arg) { this._name = arg; }
get country() { return this._country; }
set country(arg){ this._country = arg; }
}
const organization = new Organization({ name: "Acme Gooseberries", country: "GB" });
result += `<h1>${organization.name}</h1>`;
organization.name = newName;

Replace Primitive with Object. Priority scattered as strings:

// Before
orders.filter(o => o.priority === "high" || o.priority === "rush");

// After
class Priority {
constructor(value) { this._value = value; }
get value() { return this._value; }
higherThan(other) {
return Priority.RANK.indexOf(this._value) > Priority.RANK.indexOf(other._value);
}
}
Priority.RANK = ["low", "normal", "high", "rush"];

// at the Order class
class Order {
constructor(data) { this._priority = new Priority(data.priority); /*...*/ }
get priority() { return this._priority; }
}

// call site
orders.filter(o => o.priority.higherThan(new Priority("normal")));

Common Confusion / Misconception

"I'll add real behavior to the wrapper later when I need it." That is the point. At first the class does almost nothing -- but you now have a place for new behavior to land. Without the class, the next format, validate, or compare gets scattered across the codebase.

Encapsulate Record ≠ mass-produce DTOs. The move is applied where there is pain: wide scope, many readers, impending rename or replacement. For a record used in one function, leave it.

How To Use It

Mechanics for Replace Primitive with Object:

  1. Apply Encapsulate Variable to the primitive field, if not already.
  2. Create the small value class.
  3. Setter constructs a new instance; getter returns instance.value (or the instance itself once callers migrate).
  4. Migrate callers one by one to call methods on the instance.
  5. Consider Change Reference to Value / Change Value to Reference based on identity semantics.

Check Yourself

  1. Why is "wrap every primitive" the wrong rule?
  2. Encapsulate Record leaves the field storage private. What new refactors become cheap after this move?
  3. A temperature is stored as a number. Name three pieces of logic that would migrate onto a Temperature class and three that would not.

Mini Drill or Application

Find a string field in your code that appears in ≥3 string-comparison conditions. Apply Replace Primitive with Object. Migrate one caller. Add a unit test for the new class's main comparison behavior. You should end up with fewer conditional expressions scattered across the codebase.

Video and Lecture References

Article References

External Exercises

Depth Path

  • Read This Only If Stuck - Fowler chunks 044 (Encapsulate Variable), 049 (Encapsulate Record part 1), 052 (Replace Primitive with Object)
  • Optional deep dive: chunk 064-065 (Replace Derived Variable with Query, Change Value to Reference)

Source Backbone

Refactoring is the canonical book backbone for this module. Use these sources after attempting the refactor and tests yourself.