# Preserve Whole Object

> Pass the object itself instead of several values pulled out of it, so the method signature stays stable as needs change.

- kind: technique
- category: simplifying-method-calls
- canonical: https://aipatterns.dev/refactoring/techniques/preserve-whole-object
- inspired-by: https://refactoring.guru/preserve-whole-object

## Problem

A caller reads several fields from one object and passes them as separate arguments. When the method needs one more field, every call site changes. The repeated `x.a, x.b, x.c` pattern appears wherever the method is called.

## Solution

**Pass the whole object and let the method read the fields it needs.**

## Before

```ts
function isWithinBusinessHours(
  openHour: number,
  closeHour: number,
  timezone: string,
  at: Date,
): boolean {
  const hour = Number(
    new Intl.DateTimeFormat("en-US", { hour: "numeric", hourCycle: "h23", timeZone: timezone }).format(at),
  );
  return hour >= openHour && hour < closeHour;
}

if (isWithinBusinessHours(clinic.openHour, clinic.closeHour, clinic.timezone, slot.start)) {
  book(slot);
}
```

## After

```ts
function isWithinBusinessHours(clinic: Clinic, at: Date): boolean {
  const hour = Number(
    new Intl.DateTimeFormat("en-US", { hour: "numeric", hourCycle: "h23", timeZone: clinic.timezone }).format(at),
  );
  return hour >= clinic.openHour && hour < clinic.closeHour;
}

if (isWithinBusinessHours(clinic, slot.start)) {
  book(slot);
}
```

## Why

- Shorter call sites with no repeated field extraction.
- When the rule later needs `clinic.holidays`, only the method changes.
- The method now shows it is about a clinic, which often hints it belongs on `Clinic` itself.

## Steps

1. Add a parameter for the whole object, alongside the existing ones. Run the tests.
2. Inside the method, replace one extracted-value parameter with a read from the object. Run the tests. Repeat for each.
3. Remove the now unused value parameters, and the code at call sites that extracted them. Run the tests.
4. If the method only uses the object's data, consider [Move Method](https://aipatterns.dev/refactoring/techniques/move-method.md) to put it on the object's class.

## Watch out

- Do not pass the whole object when the method lives in a lower layer that should not depend on the object's type. A pricing library should not import your ORM entity. Pass values, or define a narrow interface (`Pick<Clinic, "openHour" | "closeHour" | "timezone">`).
- Passing a mutable object hands the method write access to all of it. Use a readonly type if it must not change anything.
- If callers sometimes build the values from different sources (not always from one object), the separate parameters are honest. Consider [Introduce Parameter Object](https://aipatterns.dev/refactoring/techniques/introduce-parameter-object.md) instead.
- No named inverse exists, but splitting a whole object back into values is the right move when you need to remove a dependency.

## Related

- Fixes [Long Parameter List](https://aipatterns.dev/refactoring/smells/long-parameter-list.md) and [Data Clumps](https://aipatterns.dev/refactoring/smells/data-clumps.md).
- Alternative: [Introduce Parameter Object](https://aipatterns.dev/refactoring/techniques/introduce-parameter-object.md) when no existing object holds the values.
- Often followed by [Move Method](https://aipatterns.dev/refactoring/techniques/move-method.md).

## Agent checklist

- [ ] Every call site took the passed values from the same object.
- [ ] The method's module may depend on that object's type, or you used a narrow structural type.
- [ ] The method cannot accidentally mutate the object it receives.
- [ ] Extraction code at call sites was removed.
