Java 8 Lambda Expression for Design Patterns – Decorator Design Pattern
The Decorator pattern (also known as Wrapper) allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. It can be considered as an alternative to subclassing. We know that subclassing adds behavior at compile time and the change affects all instances of the original class. On the other hand, decorating can provide new behavior at run-time for selective objects.
The decorator conforms to the interface of the component it decorates so that it is transparent to the component’s clients. The decorator forwards requests to the component and may perform additional actions before or after forwarding. Transparency allows decorators to be nested recursively, thereby allowing an unlimited number of added responsibilities. The key participants of the Decorator pattern are represented below:
- Component – Specifies the interface for objects that can have responsibilities added to them dynamically.
- ConcreteComponent – Defines an object to which additional responsibilities can be added
- Decorator – Keeps a reference to a Component object and conforms to Component’s interface. It contains the Component object to be decorated.
- ConcreteDecorator – Adds responsibility to the component.
Now let’s look at a concrete example of the decorator pattern and see how it gets transformed with lambda expressions. Suppose we have different types of books and these books may be in different covers or different categories. We can chose any book and specify category or language by using inheritance. Book can be abstracted as a class. After that any other class can extend Book class and override the methods for cover or category. But this is not an efficient approach. Under this approach, the sub-classes may have unnecessary methods extended from super class. Also if we had to add more attributes or characterization there would be a change in parent class. Changes in implementation of a class should be the last resort.
Let’s take an optimal approach by using Decorator pattern. We will make an interface for Book with a basic method:
public interface Book { public String describe(); }
A BasicBook class can implement this interface to provide minimal abstraction:
public class BasicBook implements Book { @Override public String describe() { return "Book"; } }
Next, lets define the abstract class BookDecorator which will act as the Decorator:
abstract class BookDecorator implements Book { protected Book book; BookDecorator(Book book) { this.book = book; } @Override public String describe() { return book.describe(); } }
The BookDecorator class conforms to the Book interface and also stores a reference to Book interface. If we want to add category as a property to Book interface, we can use a concrete class which implements BookDecorator interface. For Fiction category we can have the following Decorator:
public class FictionBookDecorator extends BookDecorator { FictionBookDecorator(Book book) { super(book); } @Override public String describe() { return ("Fiction " + super.describe()); } }
You can see that FictionBookDecorator adds the category of book in the original operation, i.e. describe. Similarly, if we want to specify Science category we can have the corresponding ScienceBookDecorator:
public class ScienceBookDecorator extends BookDecorator { ScienceBookDecorator(Book book) { super(book); } @Override public String describe() { return ("Science " + super.describe()); } }
ScienceBookDecorator also adds the category of book in the original operation. There can also be another set of Decorators which indicate what type of cover the book has. We can have SoftCoverDecorator describing that the book has a soft cover.
public class SoftCoverDecorator extends BookDecorator { SoftCoverDecorator(Book book) { super(book); } @Override public String describe() { return (super.describe() + " with Soft Cover"); } }
We can also have a HardCoverDecorator describing the book has a hard cover.
public class HardCoverDecorator extends BookDecorator { HardCoverDecorator(Book book) { super(book); } @Override public String describe() { return (super.describe() + " with Hard Cover"); } }
Now let’s combine all classes and interfaces defined to leverage the power of Decorator pattern. See just one sample interplay of all these classes:
import java.util.List; import java.util.ArrayList; public class BookDescriptionMain { public static void main(String [] args) { BasicBook book = new BasicBook(); //Specify book as Fiction category FictionBookDecorator fictionBook = new FictionBookDecorator(book); //Specify that the book has a hard cover HardCoverDecorator hardCoverBook = new HardCoverDecorator(book); //What if we want to specify both the category and cover type together HardCoverDecorator hardCoverFictionBook = new HardCoverDecorator(fictionBook); //Specify book as Science category ScienceBookDecorator scienceBook = new ScienceBookDecorator(book); //What if we want to specify both the category and cover type together HardCoverDecorator hardCoverScienceBook = new HardCoverDecorator(scienceBook); //Add all the decorated book items in a list List<Book> bookList = new ArrayList<Book>() { { add(book); add(fictionBook); add(hardCoverBook); add(hardCoverFictionBook); add(scienceBook); add(hardCoverScienceBook); } }; //Traverse the list to access all the book items for(Book b: bookList) { System.out.println(b.describe()); } } }
Running this gives the following output:
Book
Fiction Book
Book with Hard Cover
Fiction Book with Hard Cover
Science Book
Science Book with Hard Cover
It clearly demonstrates how different properties can be added to any pre-defined class / object. Also, multiple properties can be combined. I have tried to combine all the decorated book items in a list and then access them by iterating over the list.
What we have seen till now is just the standard decorator pattern and it’s been around for a long time now. In these times when functional programming is the new buzzword one may ponder whether the support of lambda expressions in Java, can things be done differently. Indeed, since the decorated interface is like a function interface, we can rehash using lambda expressions in Java. Lets see how the code looks like:
import java.util.List; import java.util.ArrayList; public class BookDescriptionMainWithLambda { public static void main(String [] args) { BasicBook book = new BasicBook(); //Specify book as Fiction category using Lambda expression Book fictionBook = () -> "Fiction " + book.describe(); //Specify that the book has a hard cover using Lambda expression Book hardCoverBook = () -> book.describe() + " with Hard Cover"; //What if we want to specify both the category and cover type together Book hardCoverFictionBook = () -> fictionBook.describe() + " with Hard Cover"; //Specify book as Science category using Lambda expression Book scienceBook = () -> "Science " + book.describe(); //What if we want to specify both the category and cover type together Book hardCoverScienceBook = () -> fictionBook.describe() + " with Hard Cover"; List<Book> bookList = new ArrayList<Book>() { { add(book); add(fictionBook); add(hardCoverBook); add(hardCoverFictionBook); add(scienceBook); add(hardCoverScienceBook); } }; bookList.forEach(b -> System.out.println(b.describe())); } }
Running this gives the similar output:
Book
Fiction Book
Book with Hard Cover
Fiction Book with Hard Cover
Science Book
Fiction Book with Hard Cover
We can see that use of lambda expressions, makes the additional classes for decorators redundant. You don’t need additional classes; simply specify additional behavior using lambda expression. However, there is support for finding the decorator again for re-use. If you have a concrete decorator class, you can reuse it next time also.
- All the code snippets can be accessed from my github repo
Reference: | Java 8 Lambda Expression for Design Patterns – Decorator Design Pattern from our JCG partner Gurpreet Sachdeva at the gssachdeva blog. |
abstract class BookDecorator has no abstract methods declared …
my bad :) delete previous comment :))