Decorator Pattern: Interview Deep Dive and Variations
Interviewers often test your understanding of Decorator through variations, edge cases, and comparison questions. Here's how to tackle them.
Common Interview Questions:
Q1: "Can you remove a decorator after adding it?" Answer: No, not directly. Decorators are wrappers added at construction time, not dynamically removable. If removal is required, maintain a list of decorators and rebuild the chain excluding the unwanted one. However, this defeats the purpose of immutability and simplicity. Alternative: use the Strategy Pattern where behaviors can be swapped, or maintain a separate registry of applied decorators and their inverse operations (like undo/redo in an editor).
Q2: "How do you prevent adding the same decorator twice?" Answer: First, at the client level, track applied decorators in a Set. Second, in the decorator itself, implement a contains(Class type) method that traverses the chain checking types. Third, use a Builder or Factory that enforces uniqueness rules. Fourth, accept that duplication might be valid (double cheese on pizza) and handle it in business logic, not design constraints.
Q3: "What if the Component interface has 20 methods?" Answer: This violates ISP (Interface Segregation Principle). Solution: First, split the interface into smaller, focused interfaces (Readable, Writable, Closeable). Second, use an abstract base decorator with default implementations that just delegate, so concrete decorators override only what they need. Third, consider whether the interface is too broad, indicating a design smell.
Q4: "Decorator vs. Composite Pattern?" Answer: Composite handles tree structures with uniform treatment of individual and composite objects (files and folders). Decorator adds responsibilities to individual objects. Key difference: Composite focuses on part-whole hierarchies (structural organization), Decorator focuses on behavior augmentation. You can use both together: decorate a composite object.
+ log(message: String): void
Class ConsoleLogger implements Logger:
+ log(message): print to console
Class TimestampDecorator extends Logger:
- logger: Logger
+ log(message): logger.log("[" + timestamp + "] " + message)
Class EncryptionDecorator extends Logger:
- logger: Logger
+ log(message): logger.log(encrypt(message))
Class FileWriterDecorator extends Logger:
- logger: Logger
- file: File
+ log(message): logger.log(message); file.write(message)
Logger logger = new FileWriterDecorator(new EncryptionDecorator(new TimestampDecorator(new ConsoleLogger())))Result: Logs are timestamped, encrypted, written to both console and file.
Variation 1: Transparent vs. Semi-Transparent Decorators. Transparent decorators only implement the Component interface and hide their identity. Semi-transparent decorators add extra methods (getBorder() on a BorderDecorator). In interviews, clarify requirements: if clients need to access decorator-specific methods, use semi-transparent but accept type casting. If full transparency is needed, stick to the interface only.
Variation 2: Mutable vs. Immutable Decorators. Standard Decorator creates new objects (immutable). If your original component is mutable and you want to modify it in place, consider the Proxy Pattern instead. In machine coding rounds, immutable decorators are easier to reason about and avoid side effects, so prefer them unless mutability is explicitly required.
Variation 3: Conditional Decorators. Sometimes decorators apply behavior conditionally. For example, CacheDecorator returns cached data if available, otherwise delegates. Pseudo-code: if (cache.has(key)) return cache.get(key); else return component.operation(). This is valid but blurs the line with Proxy. In interviews, explain that you are still adding behavior (caching), not just controlling access.
MilkCoffee, MilkWhipCoffee, MilkWhipCaramelCoffee), they will dock points. Immediately pivot to composition: "I realize this creates a class explosion. Let me refactor to use Decorator Pattern instead."Performance Considerations: Each decorator adds a layer of method calls (indirection). In performance-critical paths, deep chains (more than 5 decorators) can cause noticeable overhead. Mitigation: First, flatten the chain by combining decorators if they are always used together. Second, use caching or memoization for expensive decorated operations. Third, profile before optimizing, as premature optimization is often unnecessary.
Testing Decorators: Unit test each decorator independently by mocking the wrapped Component. Integration test the full chain. Example: Test TimestampDecorator with a mock logger that records calls, verify timestamp format. Then test the full chain: FileWriterDecorator(EncryptionDecorator(TimestampDecorator(ConsoleLogger))) and assert all behaviors are applied in order.
Real-World Scenarios to Practice: First, notification system: SMS, Email, Push notifications with priority, logging, retry decorators. Second, pricing engine: base price with seasonal discount, bulk discount, loyalty discount decorators. Third, data validation: required field, format validation, custom rules as decorators. Fourth, middleware in web frameworks: authentication, logging, compression, rate limiting as decorators around request handlers.