DEV Community

jzfrank
jzfrank

Posted on

HFDP(3) - Decorator Pattern

In this post we discuss the decorator pattern.

Imagine you are running a coffee shop. Previously, you sell beverage like Espresso, HouseBlend, DarkRoast. Now many customers say they want coffee with different condiments, like milk, mocha, soy, whip. How should you upgrade your system (codebase) to model different "coffee type"?

First naive approach would be defining new classes: DarkRoastWithMilk, DarkRoastWithMilkAndSoy, DarkRoastWithSoy, ... etc. (you get the idea). However, if we allow customer to customize their coffee, we actually need to define many many many coffee classes! That is classes explosion! Well, this approach almost certainly is NOT gonna work!

classes explosion

Second slightly more well-thought approach is extending the superclass Beverage. So that we supplement it with fields like hasMilk, hasSoy (and corresponding setters and getters). This is better than approach one. However, subclasses may not necessarily want to inherit every fields/methods from superclass. Additionally, what if the customer want to order double mocha coffee?

Second Approach

Wouldn't it be nice to have a way to add condiment to coffee at runtime, instead of extending classes definition? That is exactly Decorator Pattern.

Formal Definition

The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

OK, there are indeed better approach than inheritance (which is good, but makes us need to manually define subclasses...)

Furthermore, decorator pattern is a good demonstration of one important design principle: Open-Closed Principle.

Design Principle

Classes should be open for extension, but closed for modification

Simply put, this principle states the user should easily add new behaviors to a class, without modifying the existing code.

Here is an illustration of Decorator Pattern in our coffee shop:

Decorating beverages

The Beverage is an abstract class, while concrete components like HouseBlend directly inherits from it. On the other hand, we have Decorator which is a subclass of Beverage the base class. Further, we define concrete decorator classes as subclasses of Decorator.

Since Decorator is essentially a wrapper to concrete components, we need to make them have the same base class. It is a bit like Matryoshka doll.

Matryoshka doll in coffee shop

Matryoshka doll in real

Example

A great example of decorator pattern is the java.io classes, which is absolutely complicated to understand at first: it just have too many classes and their connections seem random. But now we understand the decorator pattern, we are in a better position to appreciate java.io classes:

java.io classes diagram

The InputSystem is the abstract base classes (of everything). On left side we have concrete components like FileInputStream. On right side we have abstract decorator FilterInputStream, which has concrete implementation like PushbackInputStream.

We can even implement our own concrete decorato:

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class LowerCaseInputStream extends FilterInputStream {
    public LowerCaseInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char) c));
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = super.read(b, off, len);
        for (int i=off; i<off+len; i++) {
            b[i] = (byte) Character.toLowerCase((char) b[i]);
        }
        return result;
    }
}

Enter fullscreen mode Exit fullscreen mode

That's about it for today. See you next design pattern!

Top comments (0)