Decorator Pattern

We know that if we want to extend functionality of a particular class, we literally “extend” that class and create a subclass. It is one of the ways to do that. However, it is not the most flexible approach. Moreover, you might encounter often the case where subclassing will turn into a nightmare. Imaging you have to implement something like ComputerParts class. There are simply too many computer parts for us to make every single one of them a separate class. The other way to do that would be to create a big class named Computer and then include all of the parts as instance variables. You can tell it is not feasible either for the same reason the last suggestion wasn’t – it would be one messy giant of a class. Plus, it would be violating good design principles, like Single Responsibility Principle and Open/Closed Principle – the class would simply have too many purposes, which is not good. And if we would want to create changes, it wouldn’t be easy to change, and it wouldn’t have the ability to be easily extended in the case when we will have extra computer parts coming our way.

One way to remedy that is to use Decorator Pattern. Let’s see how can we apply it to the problem of computer parts representation. The reason why it is called a Decorator pattern is because is “decorates” an object, i.e. gives it extra functionality. In our example we might need to represent graphics card: it has a board, it has GPU, it has a fan or two, it has sockets, and many more items. Separately all of them are also computer parts, just like the graphics card itself. We are going to take our GPU, and wrap it with board, then wrap all of that with a fan, and a socket. All of these separate computer parts can have a similar function, like checkCondition(), which will return a value between 0 and 100 percent on how durable it still is. By wrapping each of those objects, we can create something like this graphics card during runtime and then combine the functionality of all smaller parts into one big one, thus extending it dynamically. Every object that wrapped the GPU object is a decorator, and they have decorated GPU giving it extra functions.


Decorator Pattern:

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


Let’s take a look at some of the details of what we have on our hands now:

  • In order for decorators to be able to wrap and extend the original object, they must be of the same super type as the object they decorate.
  • We can use anywhere from one to however many decorators you want on an object.
  • Decorated object in turn can be decorated further. Since it is the same super type as original object, decorators do not see the difference and thus can be applied to it as well.
  • We can add decorators at the runtime. That makes functionality extension very dynamic.

All of these features give us great flexibility is terms of defining end object at the runtime. If tomorrow we are going to have a new graphics card with several GPUs, 3 fans, and a box instead of board, we can easily create that. To do that, we simply create a new decorator named Box, and that’s it. All of the rest decorators already exist.

// Source: https://en.wikipedia.org/wiki/Decorator_pattern

// The Window interface class
public interface Window {
    void draw(); // Draws the Window
    String getDescription(); // Returns a description of the Window
}

// Implementation of a simple Window without any scrollbars
class SimpleWindow implements Window {
    @Override
    public void draw() {
        // Draw window
    }
    @Override
    public String getDescription() {
        return "simple window";
    }
}

// abstract decorator class - note that it implements Window
abstract class WindowDecorator implements Window {
    private final Window windowToBeDecorated; // the Window being decorated

    public WindowDecorator (Window windowToBeDecorated) {
        this.windowToBeDecorated = windowToBeDecorated;
    }
    @Override
    public void draw() {
        windowToBeDecorated.draw(); //Delegation
    }
    @Override
    public String getDescription() {
        return windowToBeDecorated.getDescription(); //Delegation
    }
}

// The first concrete decorator which adds vertical scrollbar functionality
class VerticalScrollBarDecorator extends WindowDecorator {
    public VerticalScrollBarDecorator (Window windowToBeDecorated) {
        super(windowToBeDecorated);
    }

    @Override
    public void draw() {
        super.draw();
        drawVerticalScrollBar();
    }

    private void drawVerticalScrollBar() {
        // Draw the vertical scrollbar
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", including vertical scrollbars";
    }
}

// The second concrete decorator which adds horizontal scrollbar functionality
class HorizontalScrollBarDecorator extends WindowDecorator {
    public HorizontalScrollBarDecorator (Window windowToBeDecorated) {
        super(windowToBeDecorated);
    }

    @Override
    public void draw() {
        super.draw();
        drawHorizontalScrollBar();
    }

    private void drawHorizontalScrollBar() {
        // Draw the horizontal scrollbar
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", including horizontal scrollbars";
    }
}

// Client
public class DecoratedWindowTest {
    public static void main(String[] args) {
        // Create a decorated Window with horizontal and vertical scrollbars
        Window decoratedWindow = new HorizontalScrollBarDecorator (
                new VerticalScrollBarDecorator (new SimpleWindow()));

        // Print the Window's description
        System.out.println(decoratedWindow.getDescription());
    }
}

If you have ever dealt with Java, you probably have already seen decorators without realizing they are the ones. In Java reading files comes in wrapping file reading streams: FileInputStream, BufferedInputStream, LineNumberInputStream. Even by just reading names you can see that each one of them has something to offer to the object they wrap.

Decorators are a great way to extend functionality besides conventional inheritance. They provide you with a chance to make it once and reuse it all the time structure. To add to that, decorator is designed to be loosely coupled – object doesn’t know what’s up with decorators and vice versa. Client wouldn’t know that it is dealing with decorator since it is the same type as the object it expects. Next time you deal with a problem of great amount of potential subclasses and you see that they can share the same type, consider Decorator pattern. It will ease your coding process by enabling greater readability and creating a more concise code.

Leave a comment