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!
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?
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:
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.
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:
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;
}
}
That's about it for today. See you next design pattern!
Top comments (0)