Introduce Parameter Object and Preserve Whole Object
What This Concept Is
Two refactor moves that shrink parameter lists by respecting the shape of data.
Introduce Parameter Object: when several parameters frequently travel together (a data clump), wrap them in a small class and pass that instead. Typical clump: (startDate, endDate), (x, y), (low, high).
Preserve Whole Object: when the caller pulls several values out of a record and passes the parts, pass the whole record instead. The function body derives what it needs.
Connection to primary concepts (Cluster 3): these are often applied immediately after Encapsulate Record or as preparation for Replace Primitive with Object. The parameter object is the new type's first class.
Why It Matters Here
Long parameter lists are a smell. Each extra parameter is a coupling to the call site's data shape. More importantly, parameters that travel together in three functions will travel together in four, five, ten -- at which point every signature change ripples across all of them.
Preserve Whole Object avoids the opposite failure: pulling aRoom.daysTempRange.low and .high out just to pass them. A day later, the function needs the midpoint too, and suddenly every caller changes.
Concrete Example
Introduce Parameter Object. Before -- a numeric-range clump:
function amountInvoiced(customer, startDate, endDate) { /* ... */ }
function amountReceived(customer, startDate, endDate) { /* ... */ }
function amountOverdue (customer, startDate, endDate) { /* ... */ }
After:
class DateRange {
constructor(start, end) { this._start = start; this._end = end; }
get start() { return this._start; }
get end() { return this._end; }
includes(date) { return date >= this._start && date <= this._end; }
}
function amountInvoiced(customer, range) { /* ... */ }
function amountReceived(customer, range) { /* ... */ }
function amountOverdue (customer, range) { /* ... */ }
This sketch assumes immutable, consistently ordered time values and inclusive endpoints. Mutable JavaScript Date objects need an ownership policy; timezone, invalid-date and reversed-range handling are not supplied by these getters.
The includes method is the first piece of behavior that now has a natural home -- behavior that otherwise would be duplicated inside each of the three call sites.
Preserve Whole Object. Before:
const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
if (!aPlan.withinRange(low, high)) alerts.push("out of range");
After:
if (!aPlan.withinRange(aRoom.daysTempRange)) alerts.push("out of range");
Now withinRange decides when and how often to read the bounds. Stable plain records can preserve the old observation; getters or mutation can change it. A new median dependency still needs a contract, even if call-site syntax stays unchanged.
Common Confusion / Misconception
"Parameter objects are always better." Not when the caller does not have the whole object. If three parameters come from three unrelated places, lumping them into a parameter object creates a ceremonial wrapper with no real meaning.
Preserve Whole Object has a limit: it creates a dependency from the called function to the whole class. If the function lives in a module that should not know about Room, do not preserve the whole -- unpack at a boundary.
How To Use It
Mechanics for Introduce Parameter Object:
- Create the class. Start with just the fields.
- Add an adapter accepting the new object while retaining the old entry point. If both forms can be supplied together, define precedence and reject ambiguity explicitly.
- Callers start passing the object; the function derives values from it.
- Remove the original parameters.
- Migrate related behavior onto the parameter class as it appears.
Check Yourself
- Name two signals that say "do not introduce a parameter object yet."
- When does Preserve Whole Object couple the called function to the wrong layer?
- How does Introduce Parameter Object set up a later Replace Primitive with Object?
Mini Drill or Application
Find a function in your code with 4+ parameters. Identify any subset that travels together in at least one other function. Introduce Parameter Object. Migrate two callers. Look for a method that would now naturally land on the new class and move it there.
Engineering Extension: Specify the parameter value
A date range needs endpoint inclusivity, time representation, invalid-input and ownership rules. Passing a larger object can delay reads or increase dependency surface. Snapshot only when the original contract already promised snapshot semantics, or classify it as a migration.
Use the behavior contracts, labs and practice feedback to test the claim.
Read This Only If Stuck
Locate this move through the Refactoring book index. The selective reading guide gives exact consulted local locators and primary documentation. Index links are not exact chapter links.