The Decorator Pattern is a structural design pattern that allows you to dynamically add behavior or responsibilities to an object without modifying its original code. It is often used to adhere to the 2. Open and Closed Principle (one of the SOLID principles), which states that classes should be open for extension but closed for modification.

Imagine you have a plain cake. The Decorator Design Pattern is like adding layers of frosting, sprinkles, and decorations to make it more delicious and fancy. It lets you enhance an object by “decorating” it with extra features while keeping everything organized and flexible.

Pizza Interface

interface Pizza{
	String getDescription();
	double cost();
}

BasicPizza

class BasicPizza implements Pizza{
	
	public String getDescription(){
		System.out.println("Basic Pizza");
	}
	
	public double cost(){
		return 8.0;
	}
}

Knowledge

An abstract class is like a blueprint for other classes. It cannot be instantiated itself but is designed to be extended by other classes. Abstract classes can have both regular methods with implementations and abstract methods that don’t have implementations and must be overridden by the subclasses.


ToppingsDecorator

// Decorator: ToppingDecorator
abstract class ToppingsDecorator implements Pizza{
	protected Pizza pizza;
	
	public ToppingsDecorator(Pizza pizza){
		this.pizza = pizza;
	}
	
	public String getDescription(){
		return pizza.getDescription();
	}
	
	public abstract double cost();
}

Knowledge

In the ToppingDecorator abstract class, the Pizza field is declared as protected to control the visibility and accessibility of this field for subclasses. Here’s why:

Visibility Control: By making Pizza field protected, it can be accessed by subclasses (like CheeseTopping, PepperoniTopping, etc.) and any other classes in the same package. This allows subclasses to directly interact with the Pizza instance they’re enhancing without exposing it to classes outside the package.

Inheritance and Encapsulation: Making the Pizza field protected ensures that subclasses can access it directly, which is important for the decorators to work. Since Pizza is the core component being decorated, decorators need access to its methods and properties to add their functionality.

Consistency: By using protected, the design enforces a consistent and controlled way for subclasses to interact with the core component.

Remember, encapsulation is a key principle in object-oriented programming, and using protected in this context helps balance visibility and control while ensuring that the decorator pattern works effectively.


CheeseToppings

// Concrete Decorator : CheeseToppings
public CheeseToppings extends ToppingsDecorator{
	public CheeseToppings(Pizza pizza){
		super(pizza);
	}
 
	public String getDescription(){
		System.out.println(pizza.getDescription() + ", Cheese");
	}
	
	public double cost(){
		return pizza.cost() + 10;
	}
}

PepperToppings

// Concrete Decorator : PepperoniTopping
public PepperoniTopping extends ToppingsDecorator{
	public PepperoniTopping(Pizza pizza){
		super(pizza);
	}
 
	public String getDescription(){
		System.out.println(pizza.getDescription() + ", Pepperoni");
	}
	
	public double cost(){
		return pizza.cost() + 12.5;
	}
}

Main.java

public class Main{
	public static void main(String[] args){
		Pizza basicPizza = new BasicPizza();
		Pizza cheesePizza = new CheesePizza(basicPizza);
		Pizza peperoniCheesePizza = new PepperoniTopping(cheesePizza);
		
		System.out.println(pepperoniCheesePizza.getDescription());
		System.out.println("Cost: $" + pepperoniCheesePizza.cost());
	}
}