Structural Pattern: Decorative Pattern

Structural Pattern: Decorative Pattern

Introduction

In object-oriented programming, the Decorative Pattern is a structural design pattern that allows us to dynamically add responsibilities to an object while maintaining the same interface. This pattern provides a flexible alternative to subclassing for extending functionality.

The Story

Imagine a hot summer day, and you’re craving a refreshing dessert. You and your friend, Xiao Ming, walk into a dessert shop and order a bowl of ice cream, but Xiao Ming wants to add honey and milk to his ice cream. The shop owner offers three types of paste: ordinary paste (without sugar), honey paste, and milk paste. Xiao Ming orders the honey paste, but you want to add honey and milk to your ice cream. The shop owner is happy to accommodate your request, but the existing system doesn’t support it.

The Problem

In this scenario, if we were to use inheritance to implement the paste system, we would need to create a new class for each combination of paste, such as honey-milk paste. This would lead to a proliferation of classes, making the system rigid and inflexible.

The Decorative Pattern

The Decorative Pattern offers a solution to this problem. Instead of creating a new class for each combination of paste, we can use a decorator to add the required responsibilities to the object dynamically. In this case, we can create a decorator that adds honey and milk to the ordinary paste.

Code Implementation

Here’s the code implementation of the Decorative Pattern:

// Abstract class that defines the abstract methods of making paste
abstract class HerbalJelly {
    public abstract void process();
}

// The most basic paste boss provided this paste without any material, that is the bitter paste, we call ordinary paste
class CommonHerbalJelly extends HerbalJelly {
    @Override
    public void process() {
        System.out.println("Sheng bowl of paste");
    }
}

// Abstract class acts as a decorator
abstract class Decorator extends HerbalJelly {
    private HerbalJelly herbalJelly;

    public Decorator(HerbalJelly herbalJelly) {
        this.herbalJelly = herbalJelly;
    }

    @Override
    public void process() {
        this.herbalJelly.process();
    }
}

// Paste of honey
class HoneyHerbalJelly extends Decorator {
    public HoneyHerbalJelly(HerbalJelly herbalJelly) {
        super(herbalJelly);
    }

    @Override
    public void process() {
        super.process();
        System.out.println("honey");
    }
}

// Paste of milk
class MilkHerbalJelly extends Decorator {
    public MilkHerbalJelly(HerbalJelly herbalJelly) {
        super(herbalJelly);
    }

    @Override
    public void process() {
        super.process();
        System.out.println("milk");
    }
}

// Test code
public class DecoratorTest {
    public static void main(String[] args) {
        CommonHerbalJelly commonHerbalJelly = new CommonHerbalJelly();
        HoneyHerbalJelly honeyHerbalJelly = new HoneyHerbalJelly(commonHerbalJelly);
        honeyHerbalJelly.process();

        MilkHerbalJelly milkHerbalJelly = new MilkHerbalJelly(honeyHerbalJelly);
        milkHerbalJelly.process();
    }
}

Results

When we run the test code, we see that Xiao Ming’s honey paste has only honey, and the red paste with honey and milk has both honey and milk.

Conclusion

The Decorative Pattern provides a flexible and scalable solution for extending functionality without creating a proliferation of classes. It allows us to add responsibilities to an object dynamically while maintaining the same interface. This pattern is widely used in the Java JDK source code, such as java.io.BufferedInputStream(InputStream). By understanding the Decorative Pattern, we can improve our design skills and create more maintainable and flexible software systems.