Refactoring techniqueOrganizing Data
Self Encapsulate Field
Access a field through its own getter and setter even inside the class, so subclasses or later changes can intercept the access.
- Also known as
- Self-Encapsulate Variable
Problem
Methods in a class read and write a field directly. Now you need to add logic whenever that field is accessed: lazy loading, a derived value in a subclass, change tracking, or a move to another object. Every direct access is a place you must edit.
Solution
Create a private (or protected) getter and setter for the field and route every access inside the class through them. Later changes then happen in one place.
Before
class Subscription {
private seats: number;
constructor(seats: number) {
this.seats = seats;
}
monthlyCost(pricePerSeat: number): number {
return this.seats * pricePerSeat;
}
addSeats(n: number): void {
this.seats = this.seats + n;
}
canInvite(currentMembers: number): boolean {
return currentMembers < this.seats;
}
}After
class Subscription {
private _seats!: number; // assigned through the setter below
constructor(seats: number) {
this.seats = seats;
}
protected get seats(): number {
return this._seats;
}
protected set seats(value: number) {
if (!Number.isInteger(value) || value < 1) throw new RangeError(`invalid seat count ${value}`);
this._seats = value;
}
monthlyCost(pricePerSeat: number): number {
return this.seats * pricePerSeat;
}
addSeats(n: number): void {
this.seats = this.seats + n;
}
canInvite(currentMembers: number): boolean {
return currentMembers < this.seats;
}
}
class TrialSubscription extends Subscription {
protected override get seats(): number {
return Math.min(super.seats, 3); // trials cap seats regardless of purchase
}
}Why
- Validation now runs on every write, including the constructor.
- A subclass changes how seats are computed by overriding one accessor.
- Moving the field later (Move Field) means editing two accessors, not every method.
Steps
- Rename the field (for example to
_seats) so direct uses stand out. - Add a getter and setter with the original name. Keep their visibility as narrow as the use requires.
- Replace each direct read and write inside the class with the accessors. Run the tests.
- Decide whether the constructor should use the setter. Use it if the setter validates; avoid it if a subclass override could run before the subclass is initialized.
Watch out
- Don't do this for every field by default. Direct access inside a small class is clearer. Apply it when you need the hook now.
- Overridable accessors called from a constructor can run subclass code against a half-built object. That is a real bug source in Java, C# and TypeScript.
- TypeScript accessors that override in a subclass need the base declared as an accessor too; a plain field cannot be overridden by an accessor.
- Inverse: remove the accessors and use the field directly when the hook is no longer needed.
Related
- Helps with Data Class by giving behavior a place to attach.
- Encapsulate Field is the external counterpart for public fields.
- Prepares for Move Field and Replace Data Value with Object.
Agent checklist
- There is a concrete need for interception (validation, override, lazy load, move).
- Every in-class access now goes through the accessors.
- Constructor use of overridable accessors was checked for initialization-order bugs.
- Accessor visibility is no wider than needed.
- Tests pass.
Treats these smells
See also
- Encapsulate FieldMake a public field private and give clients accessor methods, so the class controls how its data is read and changed.
- Replace Data Value with ObjectTurn a primitive field that has grown rules and companions into a small class that owns its data and behavior.
- Move FieldMove a field to the class that uses it most, updating every reader and writer to reach it there.
- Replace Type Code with SubclassesTurn each value of an immutable type code that drives conditionals into a subclass, and move the branch logic into overrides.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.