Introduction
In this article, we will solve another Java interview question that focuses on a design pattern commonly used in production-grade applications.
Specifically, we will explore the Decorator Pattern, a structural pattern that allows for the dynamic extension of object behavior.
This pattern is highly beneficial in scenarios where multiple combinations of functionalities are needed, without the complications of inheritance.
🚀Preparing For Java Interview? Checkout Grokking the Java Interview. 🚀
This book aims to bridge that gap by introducing classical Java interview questions on these crucial topics. It is designed for programmers preparing for Java interviews, providing frequently asked questions with detailed answers and explanations. Use this book to revise key Java concepts or deepen your knowledge to get ready for your next Java interview.
Question
Explain the Decorator Pattern.
Provide a Java implementation for an online payment system where different payment types (like Credit Card, PayPal, or Bank Transfer) can be customized with additional features like fraud detection, discount offers, or payment logging.
How does the decorator pattern help design this solution?
Follow-up points to consider:
Describe the role of components and decorators in the pattern.
Discuss the advantages of using the Decorator Pattern compared to extending different payment classes with various features.
Provide code examples to demonstrate how you would implement a basic payment class and add decorators for additional features.
Solution
Explanation of the Decorator Pattern:
The Decorator Pattern is a Structural design pattern that dynamically allows behavior to be added to an individual object. This pattern is often used to adhere to the Open/Closed Principle, as it allows extending the functionality of classes without modifying their code.
In the context of an online payment system, we might have different types of payment methods, such as Credit Card, PayPal, or Bank Transfer. Each payment type might need additional features such as fraud detection, discount offer, or payment logging. Using inheritance to add these features could lead to an explosion of subclasses for each combination of payment types and features. Instead, the Decorator Pattern allows us to wrap these features around existing payment objects in a flexible way.
Java Implementation
Let's implement our example using the Decorator Pattern.
Creating the Payment Interface:
public interface Payment {
void makePayment();
}
2. Creating Concrete Payment Classes:
Let’s implement different payment methods CreditCardPayment
and PayPalPayment
.
public class CreditCardPayment implements Payment {
@Override
public void makePayment() {
System.out.println("Processing payment through Credit Card.");
}
}
public class PayPalPayment implements Payment {
@Override
public void makePayment() {
System.out.println("Processing payment through PayPal.");
}
}
3. Creating PaymentDecorator
Abstract Class:
The PaymentDecorator
class implements Payment
and contains a reference to a Payment
object. This allows us to wrap additional features around existing payment types.
public abstract class PaymentDecorator implements Payment {
protected Payment decoratedPayment;
public PaymentDecorator(Payment decoratedPayment) {
this.decoratedPayment = decoratedPayment;
}
@Override
public void makePayment() {
decoratedPayment.makePayment();
}
}
4. Creating Concrete Decorators:
Let’s implement concrete decorators for additional features like FraudDetectionDecorator
and LoggingDecorator
.
public class FraudDetectionDecorator extends PaymentDecorator {
public FraudDetectionDecorator(Payment decoratedPayment) {
super(decoratedPayment);
}
@Override
public void makePayment() {
addFraudDetection();
super.makePayment();
}
private void addFraudDetection() {
System.out.println("Applying fraud detection checks.");
}
}
public class LoggingDecorator extends PaymentDecorator {
public LoggingDecorator(Payment decoratedPayment) {
super(decoratedPayment);
}
@Override
public void makePayment() {
logPayment();
super.makePayment();
}
private void logPayment() {
System.out.println("Logging payment details.");
}
}
5. Demonstrating Usage:
Let’s use the decorators to add features to a payment.
public class PaymentSystem {
public static void main(String[] args) {
// Basic Credit Card Payment
Payment creditCardPayment = new CreditCardPayment();
System.out.println("Basic Credit Card Payment:");
creditCardPayment.makePayment();
System.out.println();
// Credit Card Payment with Fraud Detection
Payment fraudProtectedPayment = new FraudDetectionDecorator(creditCardPayment);
System.out.println("Credit Card Payment with Fraud Detection:");
fraudProtectedPayment.makePayment();
System.out.println();
// PayPal Payment with Fraud Detection and Logging
Payment payPalPayment = new PayPalPayment();
Payment payPalWithFeatures = new LoggingDecorator(new FraudDetectionDecorator(payPalPayment));
System.out.println("PayPal Payment with Fraud Detection and Logging:");
payPalWithFeatures.makePayment();
}
}
Output:
Basic Credit Card Payment:
Processing payment through Credit Card.
Credit Card Payment with Fraud Detection:
Applying fraud detection checks.
Processing payment through Credit Card.
PayPal Payment with Fraud Detection and Logging:
Applying fraud detection checks.
Processing payment through PayPal.
Logging payment details.
Benefits of Using the Decorator Pattern
Flexibility: The Decorator Pattern provides the ability to add or remove features to an object at runtime. Instead of creating multiple subclasses for every combination of features, we can compose them dynamically.
Open/Closed Principle: The payment classes remain unchanged when adding new functionality. New decorators can be created without modifying existing code.
Composability: Different combinations of decorators can be applied to different payment methods, allowing maximum reuse of code.
Conclusion
By using the Decorator Pattern, we can keep our code more maintainable and extensible compared to using the inheritance-based approach, which would result in a large number of subclasses to support all the possible combinations of features.
> Looking to stay ahead in the world of Java development?
🚀 Subscribe to the Java Newsletter!🚀
> For Java interview questions, follow interview prep 101.