Wanna Cache? Decorate!!

The Decorator Pattern

Class Diagram:

Decorator Pattern is one of the most powerful Design Patterns, and its important to understand why. It allows you to add functionality to a Class without changing it. You use delegation instead of inheritance by just wrapping the existing Class in a "Suite" of your choice while the Interface stays the same. This way, you can even wrap a Class into multiple layers of Decorators where every Layer has its own and different purpose, and the Original Class even stays the same. Other than that the Layers are completely interchangeable and removable, because they all have the same Interface.
You can use your naked Object in the same manner as a decorated one with multiple Layers, and the rest of the Application wont even notice. Other components dont need to know whether a Class is decorated or not, because the only thing they care about is the Interface. The following Graphic should clear things up:
Just follow the Steps:
In the first example there is no Decorator. So SomeClass directly speaks to BookService
In the second example there is a single Decorator. Therefore the following steps apply
  1. SomeClass calls SomeDecoratorA.
  2. SomeDecoratorA delegates the call to the real BookService. The Decorator could even manipulate the call in this step.
  3. BookService returns a result. SomeDecoratorA now holds the result, and may manipulate it.
  4. Finally, SomeDecoratorA hands the result over to the original caller SomeClass.
In the third example, there are even 2 Nested Decorators. Since every Decorator speaks the Interface of the BookService Class you can wrap an infinite amount of Decorators upon.

Usage Examples

Most examples in Books and online Guides describe the Decorator Pattern used by some Graphic related stuff. You would have a normal line, and a line decorated with a shadow. Another example would be a Window in a GUI where you vary Decorators that add different kinds of styles (e.g. different Colors).

But its important to understand, that Decorators are not intended to just make Styling more dynamic. It is just one way to use them. Instead, there are many more great use cases for Decorators and i will show you an example applying cache functionality.

Applying Cache with Decorator Pattern

Class Diagram:

As you can see i am adding a BookServiceCacheDecorator that holds a Cache Component which temporarily stores a List of Books. The BookServiceCacheDecorator in the first place asks the Cache if there is a result available. If thats true, the decorator will use the cached result. Otherwise it will fetch the Result of the BookService itself, and store the Result in the Cache for later use.

I start with the BookService Interface,

1
2
3
public interface BookService {
public List<Book> findAll();
}

and its prime implementation.

 1
2
3
4
5
6
7
8
9
10
11
12
public class BookServiceImpl implements BookService {

@Override
public List<Book> findAll() {
System.out.println("load new data");
List<Book> books = new ArrayList<Book>();
books.add(new Book("BookA"));
books.add(new Book("BookB"));
books.add(new Book("BookC"));
return books;
}
}

Here is the Code of the Cache Class. In this example it's caching forever, but thats alright.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Cache {

private List<Book> storage;

public void save(List<Book> books){
this.storage = books;
}

public boolean hasResult(){
if(storage != null){
return true;
}

return false;
}

public List<Book> load(){
System.out.println("load from cache");
return storage;
}
}

Now lets continue with the Decorator Base Class. It has a preemtive Constructor to hand over the decorated Object.

1
2
3
4
5
6
7
8
abstract public class BookServiceDecorator implements BookService{

protected BookService decorated;

public BookServiceDecorator(BookService decorated){
this.decorated = decorated;
}
}

And finally the CacheDecorator. It Checks the Cache before calling BookServiceImpl.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class BookServiceCacheDecorator extends BookServiceDecorator {

private Cache cache;

public BookServiceCacheDecorator(BookService decorated) {
super(decorated);
}

public BookServiceCacheDecorator(BookService decorated, Cache cache) {
super(decorated);
this.cache = cache;
}

@Override
public List<Book> findAll() {
if(cache.hasResult()){
return cache.load();
}
List<Book> books = decorated.findAll();
cache.save(books);
return books;
}
}

Now two different use cases. One without Decorator, and one with the Cache Decorator:

1
2
3
4
5
6
7
8
9
BookService bookService = new BookServiceImpl();
bookService.findAll(); // prints: load new data
bookService.findAll(); // prints: load new data
bookService.findAll(); // prints: load new data

BookService cachedBookService = new BookServiceCacheDecorator(new BookServiceImpl(), new Cache());
cachedBookService.findAll(); // prints: load new data
cachedBookService.findAll(); // prints: load from cache
cachedBookService.findAll(); // prints: load from cache

Thats basically it. Just read the Code carefully. The final use cases should make clear how the Decorator applies functionality. Decorator pattern really helps you create a very modular Code in many circumstances. You should not be afraid of the additional Classes the Pattern depends on, since they are really not a big deal. The modularity gained easily outweighs them.

Comments

Popular posts from this blog

8 Things you should not be afraid of as a Developer

News

Why REST is so important