Decorator
Decorator adds behavior around an object while preserving the same client-facing operation. Wrappers compose dynamically, and their order can matter.
What you’ll understand
- Decorator adds behavior around an object while preserving the same client-facing operation. Wrappers compose dynamically, and their order can matter.
- Each decorator holds the previous Price component and adds its own contribution when price() is called.
- Evaluate the tradeoff: Nested wrappers can be harder to inspect, and order matters for noncommutative operations such as discounts and taxes. Keep the preserved contract explicit.
Useful first: Classes and Objects
The idea and when to recognize it
Decorator adds behavior around an object while preserving the same client-facing operation. Wrappers compose dynamically, and their order can matter.
- Requirement: A product price can include wrapping and engraving without one subclass for every combination.
- Change to anticipate: Combine both options and charge each requested feature exactly once.
- Design goal: Each decorator holds the previous Price component and adds its own contribution when price() is called.
Start with the concrete problem
A product price can include wrapping and engraving without one subclass for every combination.
Combine both options and charge each requested feature exactly once.
| Direct implementation | Revised design |
|---|---|
| An exclusive if/else accepts one add-on but drops another requested feature. | Each decorator holds the previous Price component and adds its own contribution when price() is called. |
| The baseline case can hide the problem. Compare the extension and boundary fixtures before accepting this design. | Nested wrappers can be harder to inspect, and order matters for noncommutative operations such as discounts and taxes. Keep the preserved contract explicit. |
Responsibilities and contracts
Each decorator holds the previous Price component and adds its own contribution when price() is called.
- Price: Defines the client operation. Public operations: price()
- AddOn: Wraps another component. Public operations: price()
Read the design
Select a participant to inspect its responsibility and public operations. Follow the labeled relationships; an arrow indicates direction, while the legend distinguishes ownership, substitution, and temporary use.
Preparing the design walkthrough…
Follow the interaction
Choose a supplied workload and compare the direct and revised implementations. Step through the same operation in both languages. Object values are recorded after each highlighted statement; previous steps remain unchanged.
Preparing the design walkthrough…
Implementation and expected output
Read the complete example alongside its input and expected output. The implementation selector changes the displayed program; the language selector preserves your current scenario and playback position.
Preparing the design walkthrough…
Try the idea in another setting
A stream can be wrapped with buffering and compression while keeping its write contract.
- Name the new client contract before adapting the example.
- Identify which state belongs to each object and who controls its lifetime.
- Write one normal case and one boundary case before extending the design.
Tradeoffs and interview takeaways
Nested wrappers can be harder to inspect, and order matters for noncommutative operations such as discounts and taxes. Keep the preserved contract explicit.
Common mistake: An exclusive if/else accepts one add-on but drops another requested feature.
- Explain the choice: Each decorator holds the previous Price component and adds its own contribution when price() is called.
- Challenge your choice: Nested wrappers can be harder to inspect, and order matters for noncommutative operations such as discounts and taxes. Keep the preserved contract explicit.
- Describe a requirement that would make the simpler implementation sufficient.
Design challenge and knowledge check
Preparing the design walkthrough…