Decorator component interface role
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
The component interface is the shared contract that makes Decorator work.
It defines the methods that both the real component and all decorators must implement. This ensures decorators can transparently wrap the component—clients interact with the interface and don't know whether they're calling the real object or a stack of decorators. Say you've got a PaymentProcessor interface with processPayment and refund methods.
StripePaymentProcessor and PayPalPaymentProcessor both implement it. Now you write LoggingPaymentDecorator. It also implements PaymentProcessor, takes a PaymentProcessor in the constructor, and delegates to it after adding logging.
The key is that the decorator holds the wrapped processor as an interface type, not a concrete class. That means you can wrap Stripe, PayPal, or even another decorator—they all implement the interface. Clients depend on PaymentProcessor, so they can use any of these interchangeably.
That's substitutability. Decorators are substitutable for the component they wrap because they implement the same interface. This is the Liskov Substitution Principle: if you can use an object of type T, you should be able to use any object of a type that implements T without breaking anything.
One mistake freshers make is creating decorators that extend the concrete class instead of implementing the interface. If LoggingPaymentDecorator extends StripePaymentProcessor, it's tightly coupled to Stripe. It won't work with PayPal, and you'll need separate decorators for every payment provider.
Another mistake is declaring the wrapped field as a concrete type like StripePaymentProcessor. That breaks reusability. Always use the interface type for the wrapped field so it accepts any implementation.
The interface is what enables transparent wrapping, runtime composition, and decorator reusability. Without it, the pattern doesn't work.
Decorator component interface role
Direct Answer
The component interface is the shared contract that both the concrete component and all decorators implement. It ensures decorators can transparently wrap the real object—clients interact with the interface, not knowing whether they're calling the real component or a stack of decorators.
Code Example
// Component interface: the shared contract
interface PaymentProcessor {
boolean processPayment(String orderId, double amount);
void refund(String orderId);
}
// Concrete component: real payment processing
class StripePaymentProcessor implements PaymentProcessor {
public boolean processPayment(String orderId, double amount) {
// Real Stripe API call
return true;
}
public void refund(String orderId) { /* Stripe refund */ }
}
// Another concrete component
class PayPalPaymentProcessor implements PaymentProcessor {
public boolean processPayment(String orderId, double amount) {
// Real PayPal API call
return true;
}
public void refund(String orderId) { /* PayPal refund */ }
}
// Decorator: works with ANY PaymentProcessor
class LoggingPaymentDecorator implements PaymentProcessor {
private final PaymentProcessor wrapped; // Interface type
LoggingPaymentDecorator(PaymentProcessor processor) {
this.wrapped = processor; // Can wrap Stripe, PayPal, or another decorator
}
public boolean processPayment(String orderId, double amount) {
System.out.println("Processing payment for " + orderId);
var result = wrapped.processPayment(orderId, amount); // Delegate
System.out.println("Payment " + (result ? "succeeded" : "failed"));
return result;
}
public void refund(String orderId) {
System.out.println("Refunding order " + orderId);
wrapped.refund(orderId);
}
}
// Usage: decorators work with any implementation
PaymentProcessor stripeWithLogging = new LoggingPaymentDecorator(
new StripePaymentProcessor()
);
PaymentProcessor paypalWithLogging = new LoggingPaymentDecorator(
new PayPalPaymentProcessor()
);
Common Mistakes & Pitfalls
- Extending concrete class instead of interface: Freshers create decorators that extend StripePaymentProcessor directly—this tight-couples the decorator to Stripe and breaks when you want to wrap PayPal or a test double. Always implement the interface.
- Using concrete type for wrapped field: Declaring wrapped as StripePaymentProcessor instead of PaymentProcessor means the decorator can only wrap that one implementation—use the interface type for the field so it accepts any conforming object.
- Skipping the interface entirely: Creating decorators without a shared contract means clients have to know whether they're calling the real component or a decorator—this breaks transparency and forces clients to depend on concrete types.
- Not understanding Liskov Substitution: Missing that the interface enables substitutability—decorators must honor the interface contract so they work anywhere the component works, which is the essence of the Liskov Substitution Principle.
Check your understanding
Answer these without looking back at the article.
- What happens if you use abstract class instead of interface as the component in Decorator?
- How does Java IO use the Decorator pattern across InputStream subclasses?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.