HFDP(3) - Decorator Pattern
Published: 2023-01-04 21:09:49

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eth2jkzksdt3mwwtboyq.png)



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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3ol1aovtkcazhhd1hv4.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rogzyyb5tryovf0dw0s6.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4u0y8n59sdx71s0kxpig.png)


![Matryoshka doll in real](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yy3951vo8q3v6qieuczq.png)


## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stnr3dk48g6nk4orgvtp.png)

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:

```java
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;
    }
}

```


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