When the information a responsibility needs is spread across several objects, how do you apply GRASP's Information Expert principle?
answer
- partial experts collaborate
- each hop = one neighbour
- delegation vs train wreck
- change locality per fragment
- pass context down, no back-pointers
basics
~20 sSplit the job. Each object computes the part it knows and asks its neighbour for the rest — a chain of "partial experts". An order asks each line item for its subtotal; each line item asks its product for the price.
solid answer
~50 sRarely does one class hold everything. Information Expert then produces a *collaboration of partial experts*: decompose the responsibility so each class computes exactly the fragment it owns and delegates the rest to the next owner. Example: `Order.total()` knows the collection of line items and sums `LineItem.subtotal()`; `LineItem` knows quantity and asks `Product.price()`. Each hop talks only to its immediate neighbour, which also satisfies the Law of Demeter and keeps every class ignorant of the others' representation. The design consequences: adding a per-line discount changes only `LineItem`; adding order-level shipping changes only `Order`. Compare with a single external calculator, where every such change edits one class that knows all three representations. Watch for: over-long delegation chains that just forward calls (a sign the model is wrong), and performance — a naive chain over thousands of children can trigger N+1 loads, which is a legitimate reason to add a projection behind the same method signature.
code
pseudocode · 13 lines// Partial experts, one hop each
class Product:
price() = unitPrice
class LineItem:
subtotal() = quantity * product.price() - lineDiscount
class Order:
total() = sum(l.subtotal() for l in lines) + shipping()
// Volatile context is passed DOWN, not fetched via back-pointers
class LineItem:
subtotal(ctx) = quantity * ctx.priceOf(product) - lineDiscountgo deeper
Say each object does its own part and asks the next one — order sums line subtotals, line item asks the product for price.
Add the term partial expert, show the change-locality benefit, and contrast a delegation chain with a getter train wreck.
Cover the failure modes: middle-man forwarding, deep chains signalling a missing concept, N+1 loading, cycles, and passing context down as a parameter.
Discuss how the ownership decomposition becomes the aggregate/module boundary, how read-side projections coexist with the write-side expert chain, and how to keep the public contract stable while the loading strategy changes.
## The situation Information Expert reads "assign the responsibility to the class that has the information needed." In practice, *no* single class usually has all of it. The principle does not break down here — it produces a *distributed* answer. ## The method 1. Write the responsibility down: "compute the grand total of an order, including per-line discounts and order-level shipping". 2. Enumerate the required information and, for each fragment, its owner: - unit price → Product - quantity, line discount → LineItem - set of lines, shipping policy → Order 3. Cut the responsibility along ownership lines. Each owner gets a method for its fragment. 4. Compose by delegation, each class calling only its direct collaborators. ``` Product.price() = unitPrice LineItem.subtotal() = quantity * product.price() - lineDiscount Order.total() = sum(line.subtotal() for line in lines) + shipping() ``` Each class is a **partial expert**. The composition is a chain (or tree) of delegations that mirrors the object graph. ## Why this is better than one calculator - **Change locality.** Introduce tiered pricing → only `Product` changes. Introduce a per-line promotion → only `LineItem`. Introduce free shipping above a threshold → only `Order`. In the external-calculator design, all three land in the same file, and that file must know all three representations. - **Encapsulation.** No class needs to expose `quantity`, `unitPrice`, or the internal collection publicly. - **Law of Demeter compliance.** LoD ("only talk to your immediate friends") forbids `order.getLines().get(i).getProduct().getPrice()`. Delegation chains satisfy it: each call crosses exactly one boundary. - **Testability.** Each fragment is testable in isolation with a trivial fixture. - **Polymorphism becomes available.** Because `subtotal()` is a method, a `SubscriptionLineItem` can override it. With external calculation you get a type switch instead. ## Important distinction: delegation chain vs. train wreck | Shape | Example | Verdict | |---|---|---| | Delegation | `order.total()` → internally calls `line.subtotal()` | Good — each hop is one step | | Train wreck | `order.getLines().get(0).getProduct().getPrice()` | Bad — caller navigates the whole graph | They look similar in a call stack but differ in *who* does the navigating. In delegation, each object navigates only its own neighbours. In a train wreck, one outsider navigates all of them and becomes coupled to every intermediate type. ## Failure modes to name 1. **Pass-through / middle-man.** If a class's method does nothing but forward (`Order.priceOf(line) = line.price()`), the layer earns nothing; the refactoring *Remove Middle Man* applies. Delegation must add something: aggregation, policy, or encapsulation. 2. **Deep chains as a modelling smell.** Five hops usually means the model has missed a concept (e.g., a `PricedOrder` or a `Quote`) or that responsibilities were sliced too thin. 3. **N+1 and lazy loading.** In persistence-backed models, a per-child delegation can issue one query per child. Cures: eager fetch, batch loading, or a read-side projection. Keep the fix behind the same method so callers still "tell". 4. **Cyclic dependencies.** If A needs B's info and B needs A's, the responsibility is misplaced or a missing concept should own both sides. 5. **Context leakage.** If a leaf needs information from far up the chain (tax jurisdiction, currency, a clock), do not give the leaf a back-pointer to the root — *pass the context down as a parameter*. `line.subtotal(pricingContext)` keeps the direction of dependency clean. ## Rule of thumb Delegate along the direction the information actually flows; pass volatile context *in* as arguments; and never let a class outside the graph do the walking.
- The line item needs a tax rate that depends on the customer's country, which only the order knows. Should LineItem hold a reference back to its Order?No — a child holding a back-pointer to its parent creates a cycle and lets the leaf reach arbitrary parent state. Pass the needed context as a parameter, e.g. line.subtotal(taxContext), or have the Order apply the tax at its own level. Dependencies should point inward/downward, and the parameter makes the requirement explicit and testable.
- How do you keep this design from causing N+1 database queries?Recognize that the correctness model and the loading strategy are separate concerns. Keep total() as the public contract, but load the graph eagerly (join fetch / batch load) or serve the number from a precomputed projection for read-heavy paths. Measure first: for small collections the naive chain is fine, and premature flattening costs you the change locality.
An assembly line: each station adds the part it owns and passes the work on. Nobody walks the whole factory collecting components to assemble alone at the end.
saying these in an interview costs you the question
- Concluding that spread-out information means Information Expert does not apply, so a god calculator is fine
- Confusing a delegation chain with a Law of Demeter violation — the difference is who navigates the graph
- Adding forwarding-only methods and calling it delegation (middle man smell)
- Giving children back-pointers to parents to fetch context instead of passing it as a parameter
- Assuming the design is slow without measuring, then flattening it preemptively