Decorator
Decorator: Adds behaviors to objects dynamically.
A structural design pattern called the Decorator Design Pattern enables the dynamic addition of functionality to specific objects without changing the behavior of other objects in the same class. To wrap concrete components, a collection of decorator classes must be created.
Components of Decorator Method Design Pattern
Below are the components of decorator method design pattern in java:
- Component Interface: An interface or abstract class that defines the core functionality. This is the base type for both concrete components and decorators.
- Concrete Component: A class that implements the Component interface and provides the basic behavior.
- Decorator: An abstract class that implements the Component interface and has a reference to a Component object. This class defines the interface for the decorators and includes a reference to a Component instance.
- Concrete Decorators: Classes that extend the Decorator class and add additional behavior to the Component.
How to Implement Decorator Design Pattern?
Below are the main steps to implement decorator design pattern:
- Define the Component Interface: Create a common interface that both concrete components and decorators will implement.
- Create Concrete Components: Implement the concrete classes that will be decorated, ensuring they adhere to the component interface.
- Create the Base Decorator: Develop an abstract decorator class that implements the component interface and holds a reference to a component object.
- Implement Concrete Decorators: Create specific decorator classes that extend the base decorator, adding their own behavior while delegating to the component object.
- Use the Decorators: Instantiate the concrete component and wrap it with decorators as needed to enhance functionality dynamically.
Example of Decorator Method Design Pattern
Below is the problem statement to understand decorator method design pattern in Java:
Imagine a pizza shop where customers can customize their pizzas with various toppings like cheese, pepperoni, mushrooms, and olives. The goal is to create a flexible system that allows you to dynamically add any combination of toppings to a base pizza without modifying the existing pizza classes or creating numerous subclasses.
- The Decorator pattern helps solve this problem by allowing you to extend the behavior of a base pizza object dynamically.
- You can create decorators for each topping, which will add its specific functionality to the base pizza.
Class Diagram of Decorator Design Pattern:
Component Wise Code of Decorator Method Design Pattern in Java:
1. Component Interface
This interface defines the core functionality of a pizza.
Java
public interface Pizza {
String getDescription();
double cost();
}
2. Concrete Component
This class implements the Pizza
interface and provides the basic implementation for a plain pizza.
Java
public class PlainPizza implements Pizza {
@Override
public String getDescription() {
return "Plain pizza";
}
@Override
public double cost() {
return 8.0; // Base price of the pizza
}
}
3. Abstract Decorator
This class implements the Pizza
interface and holds a reference to a Pizza
object. It delegates operations to the wrapped pizza object.
Java
public abstract class PizzaDecorator implements Pizza {
protected Pizza decoratedPizza;
public PizzaDecorator(Pizza decoratedPizza) {
this.decoratedPizza = decoratedPizza;
}
@Override
public String getDescription() {
return decoratedPizza.getDescription();
}
@Override
public double cost() {
return decoratedPizza.cost();
}
}
4. Concrete Decorators
These classes extend the PizzaDecorator
and add specific toppings.
Java
public class CheeseDecorator extends PizzaDecorator {
public CheeseDecorator(Pizza decoratedPizza) {
super(decoratedPizza);
}
@Override
public String getDescription() {
return decoratedPizza.getDescription() + ", cheese";
}
@Override
public double cost() {
return decoratedPizza.cost() + 1.5; // Cost of cheese topping
}
}
public class PepperoniDecorator extends PizzaDecorator {
public PepperoniDecorator(Pizza decoratedPizza) {
super(decoratedPizza);
}
@Override
public String getDescription() {
return decoratedPizza.getDescription() + ", pepperoni";
}
@Override
public double cost() {
return decoratedPizza.cost() + 2.0; // Cost of pepperoni topping
}
}
public class MushroomDecorator extends PizzaDecorator {
public MushroomDecorator(Pizza decoratedPizza) {
super(decoratedPizza);
}
@Override
public String getDescription() {
return decoratedPizza.getDescription() + ", mushrooms";
}
@Override
public double cost() {
return decoratedPizza.cost() + 1.0; // Cost of mushroom topping
}
}
public class OliveDecorator extends PizzaDecorator {
public OliveDecorator(Pizza decoratedPizza) {
super(decoratedPizza);
}
@Override
public String getDescription() {
return decoratedPizza.getDescription() + ", olives";
}
@Override
public double cost() {
return decoratedPizza.cost() + 0.75; // Cost of olive topping
}
}
Combined Code:
Below is the whole combined code of decorator method design pattern in java:
Java
// Pizza.java
public interface Pizza {
String getDescription();
double cost();
}
// PlainPizza.java
public class PlainPizza implements Pizza {
@Override
public String getDescription() {
return "Plain pizza";
}
@Override
public double cost() {
return 8.0; // Base price of the pizza
}
}
// PizzaDecorator.java
public abstract class PizzaDecorator implements Pizza {
protected Pizza decoratedPizza;
public PizzaDecorator(Pizza decoratedPizza) {
this.decoratedPizza = decoratedPizza;
}
@Override
public String getDescription() {
return decoratedPizza.getDescription();
}
@Override
public double cost() {
return decoratedPizza.cost();
}
}
// CheeseDecorator.java
public class CheeseDecorator extends PizzaDecorator {
public CheeseDecorator(Pizza decoratedPizza) {
super(decoratedPizza);
}
@Override
public String getDescription() {
return decoratedPizza.getDescription() + ", cheese";
}
@Override
public double cost() {
return decoratedPizza.cost() + 1.5; // Cost of cheese topping
}
}
// PepperoniDecorator.java
public class PepperoniDecorator extends PizzaDecorator {
public PepperoniDecorator(Pizza decoratedPizza) {
super(decoratedPizza);
}
@Override
public String getDescription() {
return decoratedPizza.getDescription() + ", pepperoni";
}
@Override
public double cost() {
return decoratedPizza.cost() + 2.0; // Cost of pepperoni topping
}
}
// MushroomDecorator.java
public class MushroomDecorator extends PizzaDecorator {
public MushroomDecorator(Pizza decoratedPizza) {
super(decoratedPizza);
}
@Override
public String getDescription() {
return decoratedPizza.getDescription() + ", mushrooms";
}
@Override
public double cost() {
return decoratedPizza.cost() + 1.0; // Cost of mushroom topping
}
}
// OliveDecorator.java
public class OliveDecorator extends PizzaDecorator {
public OliveDecorator(Pizza decoratedPizza) {
super(decoratedPizza);
}
@Override
public String getDescription() {
return decoratedPizza.getDescription() + ", olives";
}
@Override
public double cost() {
return decoratedPizza.cost() + 0.75; // Cost of olive topping
}
}
// PizzaShop.java
public class PizzaShop {
public static void main(String[] args) {
Pizza pizza = new PlainPizza();
System.out.println(pizza.getDescription() + " $" + pizza.cost());
pizza = new CheeseDecorator(pizza);
System.out.println(pizza.getDescription() + " $" + pizza.cost());
pizza = new PepperoniDecorator(pizza);
System.out.println(pizza.getDescription() + " $" + pizza.cost());
pizza = new MushroomDecorator(pizza);
System.out.println(pizza.getDescription() + " $" + pizza.cost());
pizza = new OliveDecorator(pizza);
System.out.println(pizza.getDescription() + " $" + pizza.cost());
}
}
Below is the explanation of the Code:
- Component Interface (
Pizza
): Defines the methodsgetDescription()
andcost()
that all concrete pizza and decorators will implement. - Concrete Component (
PlainPizza
): Implements thePizza
interface and represents a basic pizza with a description and cost. - Abstract Decorator (
PizzaDecorator
): Implements thePizza
interface and contains a reference to aPizza
object. This class forwards method calls to the wrapped pizza object and can be extended by concrete decorators. - Concrete Decorators (
CheeseDecorator
,PepperoniDecorator
,MushroomDecorator
,OliveDecorator
): ExtendPizzaDecorator
and add specific toppings. Each decorator modifies the description and cost of the pizza by adding the new topping. - Client Code (
PizzaShop
): Demonstrates how to create aPlainPizza
and then decorate it with various toppings. Each topping is added dynamically, and the description and cost are updated accordingly.
How Decorator Pattern Helps:
- You can add or remove toppings dynamically at runtime without changing the core pizza implementation.
- New toppings can be introduced easily by creating new decorator classes.
- The base pizza implementation remains simple and focused, while the decorators handle the additional features. This avoids a large number of subclasses for every possible combination of toppings.
This example illustrates how the Decorator pattern can be effectively used to dynamically enhance the functionality of objects while keeping the design modular and maintainable.
Pros of Decorator Design Pattern
Below are the pros of decorator design pattern:
- The decorator pattern allows you to add new functionality to existing objects without modifying their structure. This means you can easily extend behavior by stacking decorators as needed.
- By using decorators, you can separate functionality into different classes. Each decorator has its own responsibility, making your code cleaner and easier to manage.
- You can change the behavior of objects at runtime by adding or removing decorators. This dynamic capability allows for more adaptable and responsive designs.
- Instead of creating numerous subclasses to achieve various combinations of functionality, decorators allow you to compose behavior through combinations of decorators. This reduces the number of classes you need to manage.
Cons of Decorator Design Pattern
Below are the cons of decorator design pattern:
- The use of multiple decorators can lead to a complex system that may be hard to understand. Tracking the flow of behavior through several layers of decorators can become confusing.
- Each decorator adds a layer of processing, which can introduce performance overhead. This is especially true if decorators perform extensive computations or operations.
- Debugging can become challenging when multiple decorators are used together. It can be hard to pinpoint where a specific behavior is being modified.
- Decorators must maintain the same interface as the original object they are decorating. If not managed carefully, this can lead to inconsistencies or unexpected behaviors.
When to Use Decorator Method Design Pattern
Below is when to use decorator method design pattern in Java:
- You need to dynamically add or modify the behavior of an object at runtime. Adding various toppings to a pizza in a pizza shop application, where toppings can be added or removed based on user preferences.
- You have a large number of combinations of functionalities that would otherwise lead to a combinatorial explosion of subclasses. If a text editor has different features like spell check, grammar check, and thesaurus, combining these features into separate subclasses would be impractical. Instead, decorators can be used to combine these features dynamically.
- You want your classes to be open for extension but closed for modification. Extending functionality of a base component (e.g., a graphical user interface component) with additional features like borders or scroll bars through decorators rather than changing the existing class.
When not to Use Decorator Method Design Pattern
Below is when not to use decorator method design pattern in Java:
- The functionality to be added is simple or fixed, and the number of extensions is limited and known in advance. If a class only needs a fixed set of functionalities that don’t change dynamically, using a decorator might introduce unnecessary complexity.
- If you need to extend a class with a fixed set of features and you don’t need dynamic or runtime modification. A basic
Vehicle
class with fixed types likeCar
,Bike
, andTruck
where inheritance can represent these variations more straightforwardly. - When the use of decorators introduces excessive complexity or makes the code harder to understand. If the system design becomes overly complex with numerous decorators that make the overall design difficult to manage and understand.