When does the refactoring "Move Embellishment to Decorator" apply, how do you perform it, and what are its limits compared with adding another subclass or another conditional?
answer
- optional extras + flags around a core job
- subclass explosion = 2^n combinations
- wrapper implements same interface, delegates, adds
- compose at wiring time, order matters
- costs: identity/instanceof, stack depth, wide interfaces
basics
~20 sApply it when a class's core job is buried under optional extras controlled by flags — logging, caching, compression, discounts. You extract each extra into a wrapper class that implements the same interface, delegates to the wrapped object, and adds its bit. Callers compose only the extras they need.
solid answer
~50 sThe smell is a class whose essential behavior is obscured by optional embellishments that only apply in some cases, usually signalled by boolean fields and `if (compressionEnabled)`-style checks scattered through methods, or by a subclass explosion covering feature combinations. Mechanics: extract each embellishment's code into its own method; extract an interface (or use the existing one) describing the core operation; create a decorator class implementing that interface, holding a reference to a wrapped instance, delegating everything and adding the embellishment around the delegated call; move the embellishment code there; delete the flag and the conditionals; assemble the needed chain at construction/DI time. Wins: the core class returns to one responsibility, embellishments compose in any subset and order, and each is independently testable. Limits: object identity and `instanceof`/type checks break through wrappers, debugging shows deep stacks, ordering becomes semantically significant and implicit, wide interfaces make decorators tedious, and per-call allocation/indirection has a (usually small) cost.
code
pseudocode · 18 linesinterface Sender { send(payload) }
class CoreSender implements Sender {
send(payload) { transport.write(serialize(payload)) } // one responsibility again
}
class CompressingSender implements Sender {
Sender inner
send(payload) { inner.send(gzip(payload)) } // embellishment around delegate
}
class AuditingSender implements Sender {
Sender inner
send(payload) { log("start"); inner.send(payload); log("done") }
}
// assembled once, at wiring time; order is meaningful
sender = new AuditingSender(new EncryptingSender(new CompressingSender(new CoreSender())))go deeper
Say a decorator wraps an object of the same interface, delegates to it, and adds one extra behavior, so optional extras stop being flags inside the core class.
Give the steps (extract embellishment, extract interface, pass-through wrapper, move code in, delete flags, wire the chain) and note that combinations compose instead of needing a subclass each.
Lead with trade-offs: identity/instanceof breakage, implicit ordering semantics, stack depth, wide-interface forwarding burden, and when Proxy/interceptors/middleware are the right industrial form.
Discuss governance — where composition is assembled and documented, how to prevent layer-ordering bugs, when to standardize on a middleware pipeline instead of ad-hoc wrappers, and observability across layers.
## The smell A class starts focused, then accumulates *embellishments* — optional behaviors layered around the core job: ``` class DataSender { boolean compress; boolean encrypt; boolean audit; send(payload) { if (audit) log("sending", payload) bytes = serialize(payload) if (compress) bytes = gzip(bytes) if (encrypt) bytes = cipher(bytes) transport.write(bytes) if (audit) log("sent") } } ``` Symptoms: **conditional complexity** (flags checked repeatedly), **feature-envy-ish clutter** where the core algorithm is hard to find, and, in the inheritance variant, a **subclass explosion** — `CompressedSender`, `EncryptedSender`, `CompressedEncryptedSender` — one class per combination (2^n). ## The destination: Decorator **Decorator** = a class that implements the *same interface* as the object it wraps, holds a reference to that object, forwards calls to it, and adds behavior before, after, or around the forwarded call. Because a decorator *is* the interface, it can wrap another decorator; embellishments compose by nesting, and combinations are built at assembly time rather than by writing a class per combination. ## Mechanics (small, green steps) 1. **Tests first**, covering each flag combination you intend to preserve. 2. **Extract the embellishment** into its own method(s) inside the original class, so each one is a single named unit. 3. **Ensure a shared interface** exists for the core operation (Extract Interface if needed) — decorators cannot exist without a type both the core and the wrapper satisfy. 4. **Create the decorator class** implementing that interface with a field of that interface type, and delegate every method verbatim (a pass-through, behavior-neutral, tests still green). 5. **Move the embellishment** from the original class into the decorator's override, wrapping the delegated call. 6. **Delete the flag and its conditionals** from the core class. 7. **Rewire construction**: `new AuditingSender(new EncryptingSender(new CompressingSender(core)))`, typically in DI/factory configuration. 8. Repeat per embellishment; the core class shrinks back to its essential job. ## Trade-offs, limits, alternatives **When another approach is better:** - **Only one embellishment, always on** → don't decorate; just make it part of the class or the caller. - **The variation is the whole algorithm, not an addition around it** → Strategy, not Decorator. - **Variations must be selected by a lifecycle mode** → State. - **The extra behavior is cross-cutting across many types** (logging, metrics, transactions) → a proxy/interceptor/AOP mechanism or middleware pipeline is the industrial-strength form of the same idea; hand-rolled decorators per class don't scale to dozens of types. **Real costs to name in an interview:** - **Identity and type checks break.** `wrapped == original` is false; `instanceof ConcreteSender` fails through the wrapper; equals/hashCode must be considered; frameworks relying on concrete types or annotations on the target class may misbehave. - **Ordering is significant but invisible.** Compress-then-encrypt and encrypt-then-compress are not the same thing (encrypted bytes barely compress). The composition order encodes semantics that no type checks; document or encapsulate assembly in one factory. - **Debuggability.** Stack traces gain a frame per layer; "which layer swallowed my exception?" is a real support cost. - **Wide interfaces are painful.** A 20-method interface means every decorator forwards 20 methods; forgetting one, or the interface gaining a method later, is a maintenance hazard (some languages mitigate with delegation keywords or dynamic proxies; an abstract forwarding base class helps). - **Allocation/indirection cost** per call — usually negligible, occasionally relevant in hot paths. - **Discoverability.** Reading the core class no longer tells you what the system does at runtime; the truth lives in wiring configuration. **Signal that you should refactor away again:** a decorator that is always present in every composition isn't optional — fold it into the core (or into a factory that everyone uses) and remove the layer.
- How is Decorator different from Proxy, since both wrap an object of the same interface?Structurally they are near-identical; the intent differs. A Decorator adds behavior the caller opted into and is designed to stack — the caller composes several. A Proxy controls access to the subject: lazy creation, remoting, caching, or permission checks, and typically the caller doesn't know it exists and there is one of them. In practice frameworks blur the line (an interceptor pipeline is decorators generated as proxies), so answer by naming intent, not shape.
- You have five optional behaviors that apply to twelve different service interfaces. Would you hand-write decorators?No — 60 hand-written wrapper classes is the wrong scale. That's the case for a cross-cutting mechanism: dynamic proxies/interceptors, an AOP aspect, or a middleware/pipeline abstraction where the behavior is written once and applied by configuration. Keep hand-written decorators for a small number of behaviors on a small number of interfaces, where explicitness beats magic.
- What tells you a decorator layer should be refactored away?If it is present in every composition it is not optional — inline it into the core or a mandatory factory. Also refactor away if the decorator reaches into the wrapped object's concrete type, if it needs state coordinated with other layers (ordering bugs follow), or if callers must bypass it, which means the interface was the wrong abstraction.
Coffee: espresso is the core object; milk, syrup, and an extra shot are decorators. You don't create a class for every drink on the menu — you wrap the espresso with whatever the customer asked for, and the order of pouring can matter.
saying these in an interview costs you the question
- "Decorator and Strategy are interchangeable" — Decorator adds behavior around the same operation; Strategy replaces the operation's algorithm.
- Assuming decorator order doesn't matter; compress-then-encrypt vs encrypt-then-compress differ materially.
- Forgetting that wrapping breaks reference equality, instanceof/type checks, and annotation-driven framework behavior on the concrete class.
- Adding a decorator layer that is always applied — that's not an optional embellishment, it belongs in the core.
- Hand-writing wrappers for cross-cutting concerns across dozens of interfaces instead of using interceptors/middleware.
- Leaving the flag field in place "for compatibility" so both the conditional and the decorator exist.