skip to content

Which run-time stand-in works when the caller holds a concrete class rather than a declared contract, and what must that class allow?

level: middleimportance: must knowfreq 55%

answer

  1. ask what the caller declares
  2. contract type or concrete type
  3. assignability, not performance
  4. extensible class, overridable member
  5. or introduce a contract instead

basics

~20 s

Only a stand-in generated as a subtype of that class can be substituted there; in a nominally typed language a contract-only stand-in is not of that type. The subtype needs an extensible class, overridable members and construction that is safe to repeat.

solid answer

~40 s

A contract-only stand-in is built from a declared contract, so it is assignable exactly where that contract is declared. If the caller's parameter names the concrete class, the stand-in is a different type and does not fit. The alternative builds a **subtype of that class** and overrides the members you want to intercept, so it is an instance of the class by the type system's own rules. That route imposes requirements on code you may not own: the class must permit extension, each intercepted member must be overridable, and creating the stand-in usually runs the class's constructor chain. The third answer, and often the right one, is to change what the callers declare - introduce a contract, have the class declare it, and the unconstrained mechanism applies again.

code

pseudocode · 9 lines
pseudocode
// (a) contract-only: build a type that declares the contract's members
standIn = synthesizeImplementing(ChatService, handler)
acceptsContract(standIn)   // ok  - standIn declares ChatService
acceptsConcrete(standIn)   // no  - standIn is not a ChatSession

// (b) generated subtype: build a type that extends the concrete class
standIn = synthesizeExtending(ChatSession, handler)
acceptsConcrete(standIn)   // ok  - standIn is a ChatSession
// requires: ChatSession extensible, members overridable, a usable constructor

go deeper

for a junior

Recall that there are two kinds of run-time stand-in - one built from a declared contract, one built as a subtype of a concrete class - and that they are not interchangeable.

for a middle

Explain the decision from the call site: the declared parameter type decides which stand-in is assignable, and the subtype route then demands an extensible class with overridable members.

for a senior

Diagnose the real case: a service whose callers name the concrete class, a class that forbids extension, and the cost of re-pointing call sites at a new contract instead.

for a principal

Frame it as a dependency question. Choosing the subtype route makes your mechanism depend on how other teams write their classes, and that dependency is invisible until it fails.

## Two ways to build a stand-in at run time The two mechanisms differ in what the synthesized type **is**, not in what it does with a call. - A **contract-only stand-in** builds a type from a declared contract - member signatures with no bodies. The new type declares that contract and nothing else. It is unrelated by type to any concrete class. - A **generated subtype** builds a type that **extends the concrete class of the target** and overrides the members to be intercepted. By the type system's rules it *is* an instance of that class. Both funnel the calls they cover into one handler. What separates them is where each is allowed to stand. ## The question that decides it is about the caller Ask what the **caller** declares, not what the target is: - If parameters, fields and return types are declared in terms of a **contract**, the contract-only stand-in slots in with nothing else changed. - If they name the **concrete class**, a contract-only stand-in is not assignable there however identical its members are. In a nominally typed language, matching members do not make matching types. This is also why the answer to *which stand-in* is a property of the call sites, not a preference. Performance is not the axis; applicability is. ## What each requires | | Contract-only stand-in | Generated subtype | |---|---|---| | Needs from the target | A declared contract the callers actually use | A class that permits extension | | Needs per intercepted member | That the contract declares it | That the member is overridable | | Construction | Builds its own type; runs no constructor of the target's class | Usually runs that class's constructor chain | | Substitutes for | The contract type | The concrete class and its supertypes | | Covers | Exactly the declared members | Exactly the members it could override | ## What the subtype inherits and the contract-only kind does not State. A generated subtype carries **every field the concrete class declares**, because it is one of them. Two shapes follow, and they behave differently: 1. **The stand-in wraps a separately constructed target.** Two objects, two sets of fields. The stand-in's own copy is initialised and then never used, and any code reading a field directly rather than going through an overridden member reads the unused copy. 2. **The stand-in is the object.** It is created through the class's own construction path with the real arguments, intercepts, then calls the **inherited** body. One object, one set of fields, nothing to keep in step. The second shape is the safer default where the mechanism allows it, because it removes the duplication rather than managing it. ## What neither mechanism covers - **A member reachable through neither route.** The contract-only kind covers exactly the declared members; the subtype covers exactly what it could override. - **A class that forbids extension**, or that offers no constructor a subtype can chain to - that closes the subtype route entirely. - **An object that already exists.** Neither mechanism retro-fits a stand-in around an instance other code is already holding; the stand-in has to be what creation hands out in the first place. - **Members belonging to the type rather than to an instance.** A class-level member is not dispatched on a receiver, so there is no override to install and nothing to intercept. ## The third answer, and one place the rules differ In an interview the strongest answer usually ends by **changing what the callers declare**. Introducing a contract, having the concrete class declare it and re-pointing the call sites converts a constrained problem into an unconstrained one: the contract-only mechanism then applies uniformly and imposes nothing on how anyone writes their class. The price is a declaration kept in step and one edit per call site. Where the callers genuinely cannot change - compiled elsewhere, or too many of them - the generated subtype is what remains, and its requirements become requirements on someone else's class. One conceptual difference is worth naming without naming any language. Where typing is **nominal**, assignability follows declared names, so the two mechanisms really are different tools. Where typing is **structural**, a type that has the right members already is the right type, and the contract-only route applies in far more places - including at call sites that were never written against a contract at all.

  • You control the callers but not the service class - can you still use a contract-only stand-in?
    Yes. Introduce a declared contract, have the concrete class declare it, and re-point the call sites at the contract. The stand-in then satisfies the type the callers use and imposes nothing on how the class is written. The cost is one declaration to keep in step and an edit per call site, which is usually cheaper than depending on properties of code you do not own.
  • How do you keep a generated subtype from carrying a second, unused copy of the target's fields?
    Do not construct a separate target. Let the generated subtype be the object: create it through the class's own construction path with the real arguments, intercept in the handler, then call the inherited member body on that same instance. One object means one set of fields and no copy to keep in step.
  • Can one stand-in satisfy two unrelated declared contracts at once?
    The contract-only mechanism can: the synthesized type is built to declare every member of every contract it is given, and one handler serves them all, distinguishing members by the description it receives. The subtype route cannot repeat that where a type may extend only one class - the single supertype is spent on the target's class.

saying these in an interview costs you the question

  • Says a contract-only stand-in can be passed where a concrete class is declared
  • Thinks a generated subtype works for any class regardless of extensibility
  • Believes the stand-in shares the target's fields rather than owning copies
  • Assumes the choice is about performance rather than about assignability
  • Forgets that introducing a contract is itself an option