Decorator – Wrapping Functionality

Jul 30, 2020 | , , , , | Programming | 0 comments

The Decorator Pattern is a Structural Design Pattern that can be used as a flexible alternative to inheritance to add responsibility at runtime. It uses composition to achieve this and is a very powerful pattern to have in your software engineering toolbox. This post is based on the pattern as described in the Head First Design Patterns book which uses beverages as an example. We will look at a similar beverage example here.

Pattern Overview

I will again use Java to demonstrate an example implementation of this pattern. The essence of the Decorator pattern is that you take an object that you would like to extend the functionality of and wrap it in an object that implements this additional functionality. You don’t change the functionality of the original object rather you add a wrapper to that object which implements this new functionality. This may not be clear here at the start but as you read through this post my hope is that you would find this pretty straightforward.

UML

The following UML Diagram is the Generic Structure of the Decorator Pattern.

There are 4 key elements to the structure of this pattern with the most important part being the AbstractDecorator which is a type of the component that you want to wrap and also has implementations that contain a reference to the component that you want to wrap. Here is a short description of each element in this pattern:

AbstractComponent – This is the component that you want to wrap with new functionality.

ConcreteComponent – This is an implementation of the component that you want to wrap with new functionality.

AbstractDecorator – This is the component that you will use to wrap the AbstractComponent. As previously stated, the most important part of understanding this pattern is realizing that this AbstractDecorator is used to extend the AbstractComponent so that it “is a” AbstractComponent.

ConcreteDecoratorA/B – These are the implementations of the AbstractDecorator. These implementations contain member variables of the AbstractComponent that are used to add responsibility to the Abstract Component.

It is also important to notice that each component in this pattern has an implementation of the operation() that you want to add responsibility to in the AbstractComponent. There can be as many of these operations as you want.

Example

As I mentioned earlier we are going to take a look at the example used in the Head First Design Patterns book which is that of a Beverage Order. I feel like this example is extremely clear and couldn’t think of anything else that would be more helpful in understanding this pattern.

In our example, there are two types of beverages, tea, and coffee. There are a number of options that can be customized on the beverages some of which apply to both some of which do not. In our case, there is the beverage size which applies to both. There is also the option to add Caramel to the coffee as well as add additional shots to the coffee. In our model, we will have a beverage order and will need to calculate a description of the beverage order as well as the price based on the variation of the beverage type and customizations made to the beverage. We can use the decorator pattern to achieve this.

UML

Here is the UML for our example:

Source Code

Now lets take a look at the Java implementation of this design to get a better idea of how this works in the code.

AbstractBeverage.java

package solutions.reformit.blog.decoratorpattern.beverages;
import java.math.BigDecimal;
public abstract class AbstractBeverage {
	public abstract BigDecimal cost();
	public abstract String description();
}

This class is very simple. Here we see the cost and description methods that will be implemented by the Beverage sub-types. These methods will also be wrapped by the Decorators.

Coffee.java

package solutions.reformit.blog.decoratorpattern.beverages;
import java.math.BigDecimal;
public class Coffee extends AbstractBeverage {
	@Override
	public BigDecimal cost() {
		return new BigDecimal("3.95");
	}
	@Override
	public String description() {
		return "Beverage: Coffee \n ";
	}
}

This is the first of our two beverages in our solution, the coffee beverage. In this class we extend the AbstractBeverage and override the Abstract methods with a simple implementation specific for coffee.

Tea.java

package solutions.reformit.blog.decoratorpattern.beverages;
import java.math.BigDecimal;
public class Tea extends AbstractBeverage {
	@Override
	public BigDecimal cost() {
		return new BigDecimal("2.95");
	}
	@Override
	public String description() {
		return "Beverage: Tea\n ";
	}
}

This is the Tea implementation of the AbstractBeverage. In this class we find the implementation of the cost() and description() methods specific to tea. For example the price for tea is different than the price for coffee and so to the description.

AbstractOptionsDecorator.java

package solutions.reformit.blog.decoratorpattern.decorators;
import solutions.reformit.blog.decoratorpattern.beverages.AbstractBeverage;
public abstract class AbstractOptionsDecorator extends AbstractBeverage {
}

This is the simplest class in our example and might not even be necessary as it would be possible for the concrete implementations of this class to extend the AbstractBeverage class directly. With that said, I’ve decided to include it in this example for consistency and clarity.

CaramelDecorator.java

package solutions.reformit.blog.decoratorpattern.decorators;
import java.math.BigDecimal;
import solutions.reformit.blog.decoratorpattern.beverages.AbstractBeverage;
public class CaramelDecorator extends AbstractOptionsDecorator {
	AbstractBeverage beverage;
	public CaramelDecorator(AbstractBeverage aBeverage) {
		this.beverage = aBeverage;
	}
	@Override
	public BigDecimal cost() {
		return this.beverage.cost().add(new BigDecimal("1.15"));
	}
	@Override
	public String description() {
		return this.beverage.description() + "Caramel ";
	}
}

This is where things start to get more interesting. Notice that this concrete implementation of the AbstractOptionsDecorator class contains a member variable that is a reference to an AbstractBeverage. Also, note that by extending the AbstractOptionsDecorator that this class is also of type AbstractBeverage which is an ancestor class. Notice that the constructor takes an AbstractBeverage as a parameter. We will see how this works when we look at the Order.java file. Also, notice that the cost() and description() methods reference the AbstractBeverage member variable to calculate the values they return. For the cost, the idea is that the specific cost of this beverage option is added to the cost of the beverage that this is wrapping and so too for the description.

ExtraShotsDecorator.java

package solutions.reformit.blog.decoratorpattern.decorators;
import java.math.BigDecimal;
import solutions.reformit.blog.decoratorpattern.beverages.AbstractBeverage;
public class ExtraShotsDecorator extends AbstractOptionsDecorator {
	private AbstractBeverage beverage;
	private Integer extraShots;
	public ExtraShotsDecorator(AbstractBeverage aBeverage, Integer extraShotsCount) {
		this.beverage = aBeverage;
		this.extraShots = extraShotsCount;
	}
	@Override
	public BigDecimal cost() {
		return this.beverage.cost().add(new BigDecimal(extraShots).multiply(new BigDecimal("0.85")));
	}
	@Override
	public String description() {
		return this.beverage.description() + this.extraShots + " Shots ";
	}
}

Notice that this decorator has a different implementation of the cost() method from the CaramelDecorator. This is an important factor in considering whether or not to use the decorator pattern. It is probably more beneficial when the functionality you want to wrap isn’t exactly the same. For example, in the CaramelDecorator we simply add the cost to the beverage member variable and return. Whereas here, we look at the number of shots multiply those by a per shot amount and then add that value to the beverage member variable cost. If instead we just added something here too without the multiplication business logic then it would probably make more sense to add these values to a list, iterate the list and add the values up. But because the business logic is different in each decorator we are not over-engineering our solution by choosing to implement the Decorator Pattern in this example. This has been a criticism of the more simplified example used in the Head First Design Patterns book. This is also noted as one of the consequences in the GOF book.

SizeDecorator.java

package solutions.reformit.blog.decoratorpattern.decorators;
import java.math.BigDecimal;
import solutions.reformit.blog.decoratorpattern.beverages.AbstractBeverage;
public class SizeDecorator extends AbstractOptionsDecorator {
	private AbstractBeverage beverage;
	private SizeEnum size;
	public SizeDecorator(AbstractBeverage aBeverage, SizeEnum aSize) {
		this.beverage = aBeverage;
		this.size = aSize;
	}
	@Override
	public BigDecimal cost() {
		String cost = "0.00";
		switch (this.size) {
		case SMALL:
			cost = "1.00";
			break;
		case MEDIUM:
			cost = "2.00";
			break;
		case LARGE:
			cost = "3.00";
			break;
		default:
			//Do Nothing
			break;
		}
		return this.beverage.cost().add(new BigDecimal(cost));
	}
	@Override
	public String description() {
		return this.beverage.description() + this.size.name() + " ";
	}
}

SizeEnum.java

package solutions.reformit.blog.decoratorpattern.decorators;
public enum SizeEnum {
	SMALL, MEDIUM, LARGE
}

Here is our third and final Decorator, in this case for the Cup size of the beverage. Notice the Enum. Both of these are used to manage the pricing on the various cup sizes. I chose to implement it this way to keep the example simple but there might be a case for applying the decorator pattern here as well. The biggest issue with this implementation is that it is not easily extendable in the case that you might want to add other sizes. if instead of using a switch/case you used individual decorators for each size and the description/cost calculations you could add as many sizes as you want beyond the current SMALL, MEDIUM, and LARGE options without having to edit any of the existing code.

Order.java

package solutions.reformit.blog.decoratorpattern;
import solutions.reformit.blog.decoratorpattern.beverages.*;
import solutions.reformit.blog.decoratorpattern.decorators.*;
public class Order {
	public static void main(String[] args) {
		AbstractBeverage coffee = new CaramelDecorator(new ExtraShotsDecorator(new SizeDecorator(new Coffee(), SizeEnum.SMALL), 2));
		System.out.println(coffee.description() + "\n PRICE = $"+coffee.cost());
		System.out.println();
		AbstractBeverage tea = new SizeDecorator(new Tea(), SizeEnum.LARGE);
		System.out.println(tea.description() + "\n PRICE = $"+tea.cost());
	}
}

This class brings everything together. You can see how the Decorator instantiations are chained together based on the beverage type and customizations of that beverage. The first instance that is created is the Base Beverage object (Coffee or tea). Upon this base object, the decorators are wrapped. Then when the coffee.cost() method is called, the value is propagated through the class call hierarchy appending the cost of the coffee, small size, 2 extra shots, and caramel to the total price. The same happens for the tea but only with the options set for that particular object. Here is what the output is when this main() method is run.

Pros and Cons

As stated in the GOF book this pattern offers a more flexable implementation that static inheritance. I hope that you can see that is the case in our example. We see that responsiblities can be added and removed at runtime simply by attaching and detatching them.

Another drawback is that this pattern does not allow you to control the order in which this functionality is processed. The only control that you do have is through the chain of decorator instantiations used to create the AbstractComponent. If you want more control over the build order of the object you might consider the builder pattern which I have previously talked about in my Builder Pattern blog post.

One of the issues I have with our example is that the object types are a little bit confused. What I mean is that the decorators of size, caramel, and shots are not beverages in and of themselves and yet they are Beverages through the inheritance hierarchy. This could be an argument for why this example is not best suited for the Decorator pattern. Alternative examples could be input stream types as mentioned in the GOF book. You could also use this when rolling out deprecated methods to help manage what classes continue to use the deprecated functionality vs wrapping the deprecated methods in decorators with a reference to the new implementation for other classes. In any case, I chose to use it anyway though because I feel like it does a good job of demonstrating the underlying principles when trying to understand how this works. The GOF book suggests that because of this you shouldn’t rely on object identity when you use decorators.

Implementations of this pattern can result in a lot of objects and it can be hard to decipher exactly what is going on unless you have intimate knowledge of the solution. This should be on your mind as you are designing this and effort should be made to keep it as simple as possible when building the model for your solution. It’s not a reason to call this an anti-pattern rather it is something to be aware of as you implement it.

Conclusion

If you are interested in running the code yourself I have published the Beverage example to Github. It can be found in the following repository https://github.com/reformit/decorator-pattern.

If your company is looking to build software and you need help doing so, software development is our bread and butter. Please feel free to request a free quote to speak with us further about how we can help you successfully build the software that your company needs.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *