The Decorator Pattern is a structural design pattern that adheres to the principle of “open - closed” principle, which states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. It involves a set of classes that wrap around a core component. These wrapper classes, known as decorators, provide additional behavior or responsibilities to the core component.
Here is a simple UML - like representation of the Decorator Pattern:
+----------------+ +----------------+
| Component | | Decorator |
+----------------+ +----------------+
| + operation() | | - component |
+----------------+ | + operation() |
^ +----------------+
| ^
| |
+----------------+ +----------------+
| ConcreteComponent| | ConcreteDecorator|
+----------------+ +----------------+
| + operation() | | + operation() |
+----------------+ +----------------+
Let’s take an example of a coffee shop application. We have a basic coffee object, and we want to add different toppings like milk, sugar, etc. dynamically.
// Component interface
interface Coffee {
String getDescription();
double cost();
}
// Concrete Component
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double cost() {
return 2.0;
}
}
// Decorator abstract class
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double cost() {
return coffee.cost();
}
}
// Concrete Decorator - Milk
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk";
}
@Override
public double cost() {
return super.cost() + 0.5;
}
}
// Concrete Decorator - Sugar
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Sugar";
}
@Override
public double cost() {
return super.cost() + 0.2;
}
}
public class Main {
public static void main(String[] args) {
// Create a simple coffee
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " costs $" + coffee.cost());
// Add milk to the coffee
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription() + " costs $" + coffee.cost());
// Add sugar to the coffee
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " costs $" + coffee.cost());
}
}
In this example, we start with a simple coffee object. Then we wrap it with different decorators (MilkDecorator and SugarDecorator) to add additional functionality (description and cost).
The Decorator Pattern is a powerful tool in Java programming that allows you to enhance the functionality of existing objects dynamically. By following the fundamental concepts, usage methods, common practices, and best practices, you can create more modular, maintainable, and flexible code. It helps in adhering to the open - closed principle and makes your code easier to extend in the future.