Lesson 2
Decorator Pattern
Decorator Pattern

In this lesson, we'll step into the world of the Decorator pattern. The Decorator pattern is a structural design pattern that allows you to dynamically add behavior to an object without altering its structure. This pattern is particularly useful when you want to add responsibilities to objects without subclassing.

What You'll Learn

In this lesson, you will master:

  • The fundamental concept of the Decorator pattern.
  • How to implement the Decorator pattern with clear, practical examples.
  • The significance of the Decorator pattern in the software design landscape.

Let's see the Decorator Pattern in action through a practical example.

Implementing the Decorator Pattern

To understand the Decorator pattern, we'll use an example involving different types of cars and decorations (or features) that can be dynamically added to them. In our example, the objective is to create customizable cars with dynamic features such as a sports car and a luxury car.

  • Component (Car Interface): This interface defines the core functionality.
  • Concrete Component (Basic Car): This is the class that we want to dynamically add features to.
  • Decorator: This abstract class wraps a Car object and implements the Car interface.
  • Concrete Decorators (Sports Car, Luxury Car): These classes extend the Decorator class to add specific features to the Car.
Step 1: Define the Component Interface

First, we define the Car interface, which declares the core method for assembling a car.

Java
1public interface Car { 2 String assemble(); 3}

In this example, Car is the component interface that declares the assemble method. All decorator classes will implement this interface.

Step 2: Implement the Concrete Component

Next, we create the BasicCar class that implements the Car interface.

Java
1public class BasicCar implements Car { 2 @Override 3 public String assemble() { 4 return "Basic Car"; 5 } 6}

Here, BasicCar provides the base functionality by implementing the assemble method to return the string "Basic Car."

Step 3: Create the Decorator

The CarDecorator class implements the Car interface and contains a reference to a Car object.

Java
1public class CarDecorator implements Car { 2 private Car decoratedCar; 3 4 public CarDecorator(Car car) { 5 this.decoratedCar = car; 6 } 7 8 @Override 9 public String assemble() { 10 return decoratedCar.assemble(); 11 } 12}

The CarDecorator class acts as a wrapper for a Car object, delegating the call to the assemble method of the wrapped object.

Step 4: Implement Concrete Decorators

The concrete decorators extend the CarDecorator class to add specific features.

Java
1public class SportsCar extends CarDecorator { 2 public SportsCar(Car car) { 3 super(car); 4 } 5 6 @Override 7 public String assemble() { 8 return super.assemble() + " + Sports Car Features"; 9 } 10}

In the SportsCar class, we extend CarDecorator and add sports car features to the assemble method.

Java
1public class LuxuryCar extends CarDecorator { 2 public LuxuryCar(Car car) { 3 super(car); 4 } 5 6 @Override 7 public String assemble() { 8 return super.assemble() + " + Luxury Car Features"; 9 } 10}

Similarly, the LuxuryCar class extends CarDecorator and adds luxury car features to the assemble method.

Step 5: Using the Decorators

Here's how you can use the SportsCar and LuxuryCar decorators to add features to a BasicCar.

Java
1public class Main { 2 public static void main(String[] args) { 3 Car sportsCar = new SportsCar(new BasicCar()); 4 System.out.println(sportsCar.assemble()); // Outputs: Basic Car + Sports Car Features 5 6 Car luxuryCar = new LuxuryCar(new BasicCar()); 7 System.out.println(luxuryCar.assemble()); // Outputs: Basic Car + Luxury Car Features 8 } 9}

In the Main class, we create a BasicCar and then wrap it with SportsCar and LuxuryCar decorators to dynamically add their respective features.

The Importance of the Decorator Pattern

The Decorator pattern is crucial in software development for several reasons:

  • Flexibility: It allows you to add or remove features dynamically at runtime without altering the original class structure.
  • Reusability: You can create multiple decorators, which can be combined with various objects, promoting code reuse.
  • Single Responsibility Principle: It enables you to divide functions into different classes, each with a single responsibility.

By mastering the Decorator pattern, you can design more adaptable and maintainable systems. It encourages the development of flexible code that can evolve with changing requirements.

Ready to solidify your understanding with some hands-on practice? Let's dive into the practice section!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.