Skip to content

Self-Encapsulate Field

When a class touches its own field directly — this.low, self.low, e.low — every internal method is wired straight to the raw storage. Self-Encapsulate Field routes even those internal reads and writes through the field’s own accessor methods. Once every access goes through one getter and one setter, that single pair becomes the place to add validation, lazy initialisation, a default, or behaviour a subclass can override.

The smell is direct field access inside the owning class in a situation where a hook would help. You want a subclass to compute the value differently, or you want to validate on every assignment, or you want to lazily build the value on first read — but the field is poked from a dozen internal spots, so there is no single seam to insert that logic. Direct access is fine until you need a hook; the moment you do, scattered this.field reads stand in your way.

A temperature range that exposes a width. Before, the getter reads the fields directly, so a subclass cannot reinterpret the bounds. After, internal code goes through accessors, and a subclass can override low() to apply a calibration offset.

// Before
class Range {
constructor(private _low: number, private _high: number) {}
width(): number {
return this._high - this._low;
}
}
// After
class Range {
constructor(private _low: number, private _high: number) {}
get low(): number { return this._low; }
get high(): number { return this._high; }
set low(v: number) { this._low = v; }
set high(v: number) { this._high = v; }
width(): number {
return this.high - this.low; // routed through accessors
}
}
class CalibratedRange extends Range {
constructor(low: number, high: number, private offset: number) {
super(low, high);
}
get low(): number { return super.low + this.offset; }
}
  1. Create a getter and (if the field is mutated) a setter for the field, if they do not already exist.
  2. Find every place the owning class reads the field directly and replace it with a call to the getter.
  3. Find every place the class writes the field directly and replace it with a call to the setter.
  4. Run your tests after each replacement, so a failure points at one tiny change.
  5. Make the raw field as private as the language allows, so only the accessors reach it.
  6. Now add your hook — validation in the setter, a default or lazy build in the getter, or an override in a subclass — confident that every access flows through it.

Reach for Self-Encapsulate Field when you need a subclass to reinterpret a value, when you want one guaranteed spot for validation or lazy initialisation, or as a preparatory step before changing how the field is stored. It pairs naturally with Replace Type Code with Subclasses, where subclasses must be able to override how a value is produced.

The cost is indirection and a little ceremony — a getter call instead of a bare field read. Many teams prefer direct access until a concrete need for a hook appears, then apply this move. In Go and Rust, where there is no inheritance, the equivalent is defining accessor methods (often behind an interface or trait) so a wrapping type can layer on its own behaviour.

What does Self-Encapsulate Field route through accessors that Encapsulate Variable does not?
Why is a single getter/setter pair valuable?
In Go or Rust, where there is no inheritance, what plays the role of an overridable accessor?
Which move does Self-Encapsulate Field most naturally prepare for?