GRASP describes two kinds of controller: a facade controller (one object representing the whole system or a major subsystem) and a use-case controller (one object per use-case scenario). What are the trade-offs, and how do you decide which to use?
answer
- facade = one object per system/subsystem
- use-case = one object per scenario (session)
- split when constructor deps stop being shared
- bloated controller = many events + does the work + much state
- facade → use-case refactor is mechanical
basics
~20 sA facade controller is one object handling all events of a system — simple, fine for small apps. A use-case controller handles one scenario each — more classes, but each stays small and cohesive. Start with a facade; split when it grows.
solid answer
~50 sA **facade controller** represents the system, a device, or a major subsystem (`PosSystem`, `PaymentGateway`) and receives many different system events. It is cheap, gives clients one obvious entry point, and is fine when the event count is small and the events share context. Its failure mode is cohesion: as events accumulate, it becomes a grab-bag of unrelated methods and mixed state, every change touches one hot class, and merge conflicts and test setup grow. A **use-case controller** (session controller) represents one scenario — `PlaceOrderHandler`, `ReturnItemHandler`. Each class has few dependencies, can hold state for a multi-step scenario, and is trivially testable; the cost is many small classes and a slightly harder-to-survey API surface. Decide by counting and by cohesion, not by taste: few events sharing state → facade; many events, or events with different dependency sets, transaction shapes, or lifecycles → use-case controllers. Refactoring facade → use case is mechanical, so starting with a facade is a legitimate default.
go deeper
Say what each variant is and give one example name for each; note that a facade is fine for a small system.
Contrast on cohesion, dependency count, statefulness, and testability, and give a concrete split signal (unused constructor dependencies per method).
Add the bloated-controller symptom list and the two cures (more controllers vs pushing logic into the domain), plus the policy angle: differing transaction/authz needs justify separate handlers.
Discuss it as API and org design — facade as a published, stable subsystem contract; per-use-case handlers to minimize change contention across teams; the hybrid (thin delegating facade over handlers); and the low cost of the facade→use-case migration as a reason not to over-engineer up front.
## The two variants, defined GRASP Controller (Craig Larman) says a system input event should be received by a non-UI object that represents **either**: ### 1. Facade controller An object standing for *the overall system*, a *root object*, a *device*, or a *major subsystem*. Names look like `OrderingSystem`, `PosSystem`, `LibraryFacade`, `PaymentGateway`. One class, many methods — one per system event it fronts. ``` class PosSystem: startSale() enterItem(itemId, qty) endSale() makePayment(amount) ``` ### 2. Use-case (session) controller An object standing for *one use-case scenario*, typically named after the use case: `ProcessSaleHandler`, `PlaceOrderUseCase`, `HandleReturn`. It receives all the events **of that one scenario** and may hold the scenario's state for its duration (a "session" — hence the alternate name). ``` class ProcessSaleHandler: // one scenario, all its steps startSale() enterItem(itemId, qty) endSale() makePayment(amount) ``` In modern codebases the use-case controller is often shrunk further to **one class per operation** — a command handler with a single `handle(cmd)` method (`PlaceOrderHandler`, `CancelOrderHandler`). That is the same idea taken to its finest grain. ## Trade-off table | Dimension | Facade controller | Use-case controller | |---|---|---| | Class count | 1 (or a few) | one per use case / operation | | Cohesion | degrades as events accumulate | high by construction | | Dependencies | union of everything the subsystem needs — often 10+ injected collaborators | only what this scenario needs — usually 2–4 | | Scenario state | awkward: state for scenario A sits next to state for B | natural: the object *is* the session | | Discoverability | excellent — one place lists the whole API | requires a naming convention or registry to survey | | Change blast radius | one hot file, merge conflicts, big test fixtures | changes land in one small file | | Test setup | must satisfy all dependencies | minimal | | Cross-cutting policy | one place to wrap | needs a convention/decorator applied uniformly | ## Decision heuristics Use a **facade** when: - the number of system events is small (a rough rule of thumb: fewer than ~5–10); - they genuinely share context and collaborators; - you're fronting an external/legacy subsystem and want one narrow seam clients can depend on (this doubles as the Pure Fabrication and Indirection principles); - you want a stable published API for a subsystem whose internals you intend to restructure. Use **use-case controllers** when: - the event count is large or growing; - different events need disjoint dependency sets (a strong cohesion signal — look at the constructor: if half the injected collaborators are unused by half the methods, split); - a scenario is multi-step and stateful; - different operations need different transaction, retry, authorization, or rate-limit policies; - multiple teams change different operations and you want to reduce contention. ## The bloated-controller smell Larman explicitly names **bloated controller** as the failure of the facade variant. Symptoms: 1. **A single class receives most/all system events** across unrelated features. 2. **The controller performs the work itself** instead of delegating — business rules in the controller body. 3. **It carries many attributes** and maintains significant system state that duplicates what domain objects should own. 4. **Low cohesion, unfocused** — a class you cannot summarize in one sentence. Cures, in order of preference: (a) add more controllers, splitting by use case; (b) push the logic itself down into domain objects (Information Expert) so the controller shrinks back to coordination. ## Migration path Going facade → use-case controllers is a mechanical, low-risk refactor: extract each method (plus the fields only it uses) into its own class, then have the facade delegate — or delete the facade. Because the direction is easy and the reverse is rarely needed, **starting with a facade in a small system is a reasonable default**, not a mistake, provided you watch the cohesion signals. The one exception: if you already know the system has dozens of operations (a typical business application), skip the intermediate step and go straight to per-use-case handlers. ## Hybrid, in practice Many real systems do both: fine-grained use-case handlers internally, with a thin facade published for external consumers who want a single, discoverable entry point. The facade then contains *only* delegation — which keeps it thin and cohesion-neutral.
- What concrete signal tells you a facade controller should be split?Cohesion measured through dependencies and change: if most methods use only a small, disjoint subset of the injected collaborators, if the class can no longer be described in one sentence, or if unrelated features keep colliding in the same file, split by use case.
- Is one class per operation (a command handler) still 'GRASP Controller'?Yes — it is the finest-grained form of the use-case controller. The principle constrains *who receives the event* (a non-UI coordinator), not how many events one class may host.
- Can a facade and use-case controllers coexist?Yes, and it is common: per-use-case handlers do the coordinating, while a thin facade exists purely to give external clients one discoverable entry point and delegates without logic of its own.
A facade controller is a small shop's single counter that handles sales, returns, and complaints. A use-case controller is a bank's separate desks — deposits here, mortgages there. Both work; the counter fails once the queue and the paperwork variety grow.
saying these in an interview costs you the question
- Claiming use-case controllers are always the correct answer and facades are an anti-pattern — a facade is one of the two sanctioned variants
- Splitting controllers by UI screen or by entity table instead of by use case
- Believing a facade is bloated merely because it has many methods — bloat is many events plus doing the work plus holding much state, i.e. low cohesion
- Thinking splitting controllers fixes bloat when the real problem is business logic that belongs in domain objects
- Assuming a use-case controller must be stateless, which defeats the 'session controller' purpose for multi-step scenarios