# Inappropriate Intimacy

> Two classes reach into each other's private fields and internals. Cut the back-and-forth by moving members or narrowing what each exposes.

- kind: smell
- category: couplers
- aliases: Inappropriate Closeness
- canonical: https://aipatterns.dev/refactoring/smells/inappropriate-intimacy
- inspired-by: https://refactoring.guru/smells/inappropriate-intimacy

## Signs

- Class `A` reads or writes fields of `B` that exist only for `A` to use, and `B` does the same to `A`.
- Fields are made `public`, `internal` or package-private "so the other class can get at them".
- Two classes hold references to each other and call back and forth during one operation.
- A subclass depends on protected fields of its parent in ways the parent never documented.
- You cannot change one class's internals without reading the other class first.

## Why it happens

- Two concepts started as one class and were split without deciding who owns what.
- A quick fix needed one value, so a field became visible instead of a method being added.
- A bidirectional association was added for convenience and both sides started using it.
- Inheritance was used to share fields, so the child grew dependent on the parent's private layout.

## Example

An `Order` and its `Invoice` edit each other's state directly.

```ts title="before.ts"
class Order {
  items: { sku: string; price: number; qty: number }[] = [];
  invoice?: Invoice;
  _discountApplied = false; // only Invoice uses this
}

class Invoice {
  total = 0;
  constructor(public order: Order) {
    order.invoice = this;
  }

  compute(): void {
    let sum = 0;
    for (const i of this.order.items) sum += i.price * i.qty;
    if (sum > 10000 && !this.order._discountApplied) {
      sum *= 0.95;
      this.order._discountApplied = true;
    }
    this.total = sum;
  }
}
```

`Invoice` walks `Order`'s item list and flips a flag that only exists for it. `Order` keeps a back-reference it never uses.

```ts title="after.ts"
type LineItem = { sku: string; price: number; qty: number };

class Order {
  private readonly items: LineItem[] = [];

  add(item: LineItem): void {
    this.items.push(item);
  }

  subtotal(): number {
    return this.items.reduce((s, i) => s + i.price * i.qty, 0);
  }
}

class Invoice {
  readonly total: number;

  constructor(order: Order) {
    const subtotal = order.subtotal();
    this.total = subtotal > 10000 ? subtotal * 0.95 : subtotal;
  }
}
```

The discount flag disappears because the invoice computes the total once. The association now runs one way.

## Treatment

- If one class uses another's fields to do work, use [Move Method](https://aipatterns.dev/refactoring/techniques/move-method.md) and [Move Field](https://aipatterns.dev/refactoring/techniques/move-field.md) until each field has one owner.
- If both classes share a cluster of state, use [Extract Class](https://aipatterns.dev/refactoring/techniques/extract-class.md) to give that state its own home.
- If the link runs both ways but only one side needs it, use [Change Bidirectional Association to Unidirectional](https://aipatterns.dev/refactoring/techniques/change-bidirectional-association-to-unidirectional.md).
- If a client reaches through one class into another, use [Hide Delegate](https://aipatterns.dev/refactoring/techniques/hide-delegate.md).
- If a subclass is too intimate with its parent, use [Replace Inheritance with Delegation](https://aipatterns.dev/refactoring/techniques/replace-inheritance-with-delegation.md).

## Payoff

- Each class can change its internals without breaking the other.
- Invariants are enforced in one place, so state stays consistent.
- The classes become testable on their own.

## When to ignore

- The two types are one concept split across files for size reasons, such as a builder and the object it builds, or a class and its private nested helper. Close access is intended there.
- Languages offer scoped access for this case: Java package-private, C# `internal`, Kotlin `internal`, Rust `pub(crate)`. Tight coupling inside one module is acceptable when the module boundary is the real API.
- Performance-critical code where an accessor layer measurably costs too much. Measure first.

## Agent checklist

- [ ] List every member each class touches on the other and decide one owner per member.
- [ ] Check for a cyclic import between the two modules before and after the change.
- [ ] Reduce visibility of each field once no outside class reads it.
- [ ] Keep public behavior identical and run the tests after each move.
- [ ] Do not break up intentionally co-located types such as builders or nested helpers.
