2.4 Decorator Pattern - Structural Design Pattern
Design patterns are vital resources in the field of software engineering that help address common design issues in software development. The Decorator Pattern is unique among these patterns in that it is a structural pattern that enables objects to be dynamically extended with new functionalities without requiring changes to their structure. This blog will examine the principles and Java implementation of the Decorator Pattern.
Understanding the Decorator Pattern
Class composition and object composition are the domain of structural design patterns, which includes the Decorator Pattern. With the help of this pattern, objects can have additional responsibilities or behaviors added to them at runtime without altering how they behave during compilation. It encourages the use of open-closed architecture, which makes it simple to add new functionality to classes without changing the existing code.
Key Components
Typically, the Decorator Pattern includes the following elements:
Component: This is an abstract class or base interface that defines the methods that concrete components and decorators will implement.
Concrete Component: This is how the Component interface is implemented in its most basic form. It outlines the fundamental features that decorators can add to.
Decorator: This abstract class keeps track of a reference to an object that is a component and implements the Component interface. Decorators can improve the behavior of the wrapped object because they share the same interface as the Components they decorate.
Concrete Decorator: These are Decorator class subclasses that give the wrapped component particular improvements.
Implementation in Java
Let's illustrate the Decorator Pattern with a simple example in Java. Suppose we have a Coffee
interface representing different types of coffee beverages. We'll then implement a basic concrete component, SimpleCoffee
, and a set of decorators to add additional features such as milk and sugar to the coffee.
// Step 1: Define the Coffee interface
interface Coffee {
double getCost();
String getDescription();
}
// Step 2: Implement the Concrete Component
class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 1.0;
}
@Override
public String getDescription() {
return "Simple coffee";
}
}
// Step 3: Implement the Decorator
abstract class CoffeeDecorator implements Coffee {
protected final Coffee decoratedCoffee;
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
// Step 4: Implement Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
@Override
public String getDescription() {
return super.getDescription() + ", with milk";
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
@Override
public String getDescription() {
return super.getDescription() + ", with sugar";
}
}
// Step 5: Client Code
public class Main {
public static void main(String[] args) {
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Cost: " + simpleCoffee.getCost() + ", Description: " + simpleCoffee.getDescription());
Coffee milkCoffee = new MilkDecorator(simpleCoffee);
System.out.println("Cost: " + milkCoffee.getCost() + ", Description: " + milkCoffee.getDescription());
Coffee sweetCoffee = new SugarDecorator(milkCoffee);
System.out.println("Cost: " + sweetCoffee.getCost() + ", Description: " + sweetCoffee.getDescription());
}
}
Output:
Cost: 1.0, Description: Simple coffee
Cost: 1.5, Description: Simple coffee, with milk
Cost: 1.7, Description: Simple coffee, with milk, with sugar
Conclusion :
One useful method for improving an object's usefulness in a dynamic and adaptable way is the Decorator Pattern. It encourages code reuse, modularity, and scalability in software systems by enabling the addition of new actions to objects during runtime. In this blog, we looked at the main ideas behind the Decorator Pattern and used a basic coffee example to show how it might be used in Java. With the help of this pattern, developers can increase the functionality of their software systems without compromising the maintainability and extensibility of the code.
Subscribe to my newsletter
Read articles from Venu Madhav Emmadi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by