Core Java

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:

decorator

  • 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

Gurpreet Sachdeva

Gurpreet is a technical leader with deep hands-on experience, and passion for building software with delightful customer experience. He is passionate about building large enterprise, cloud scale, and customer centric software.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
adrian bob
adrian bob
3 years ago

abstract class BookDecorator has no abstract methods declared …

adrian bob
adrian bob
3 years ago

my bad :) delete previous comment :))

Back to top button