Skip to content
aipatterns.dev

Refactoring techniqueOrganizing Data

Change Value to Reference

Replace many identical copies of an entity with one shared instance looked up from a registry, so updates are seen everywhere.

Problem

Each order holds its own copy of a Customer object built from the same data. When one copy changes (a new credit limit, a renamed company), the others stay stale. The data is really one entity, but the code treats it as many values.

Solution

Keep one instance per identity in a repository or registry, and have every owner hold a reference to that instance instead of creating its own.

Before

TypeScript
class Customer {
  constructor(readonly id: string, public creditLimit: number) {}
}

class Order {
  readonly customer: Customer;
  constructor(customerId: string, creditLimit: number, public total: number) {
    this.customer = new Customer(customerId, creditLimit);
  }
}

const a = new Order("C-77", 5_000, 1_200);
const b = new Order("C-77", 5_000, 3_900);
a.customer.creditLimit = 10_000; // b still sees 5_000

After

TypeScript
class Customer {
  constructor(readonly id: string, public creditLimit: number) {}
}

class CustomerRegistry {
  private byId = new Map<string, Customer>();

  get(id: string, creditLimit: number): Customer {
    let c = this.byId.get(id);
    if (!c) {
      c = new Customer(id, creditLimit);
      this.byId.set(id, c);
    }
    return c;
  }
}

class Order {
  constructor(readonly customer: Customer, public total: number) {}
}

const customers = new CustomerRegistry();
const a = new Order(customers.get("C-77", 5_000), 1_200);
const b = new Order(customers.get("C-77", 5_000), 3_900);
a.customer.creditLimit = 10_000; // b sees 10_000

Why

  • One update reaches every holder. No sync code, no stale copies.
  • Less memory when many objects share the same entity.
  • Identity checks (a.customer === b.customer) now mean "same customer".

Steps

  1. Decide where shared instances live: an existing repository, a unit-of-work, or a new registry. Avoid a global singleton if a scoped object will do.
  2. Add a lookup that returns the existing instance for an id or creates it once.
  3. Change the owner's constructor to accept a Customer instead of building one. Update callers to fetch it from the registry. Run the tests.
  4. Remove the old creation path.

Watch out

  • Shared mutable state means any holder can change the entity for everyone. Make sure that is the intended semantics.
  • The registry's lifetime matters. A process-wide cache in a server leaks data between requests and tenants; scope it to a request or transaction.
  • In distributed systems, a reference is often an id plus a fetch, not a pointer. Don't hold stale shared objects across network boundaries.
  • If the object never changes, keep it as a value; sharing gives no benefit.
  • The inverse is Change Reference to Value.

Agent checklist

  • The object is an entity with identity and changes that all holders must see.
  • The registry's scope (request, transaction, process) matches the data's ownership.
  • Owners receive instances instead of constructing them.
  • No caller still builds its own copy.
  • Tests pass, including one that shows an update is visible through every holder.

Treats these smells

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.