In this post, we will learn Decorator Pattern with examples from Head First Design Patterns Book.
Table of contents
Table of contents
- Overview
- Class Diagram
- Implementation with Example
- Conclusion (Source code on Github Repository)
- References
1. Overview
The Decorator Pattern defined
The Decorator Pattern attaches additional responsibilities to an object dynamically.
Decorators provide a flexible alternative to subclassing for extending functionality.
2. Class Diagram
Component - Interface for objects that can have responsibilities added to them dynamically.
Concrete Component - Defines an object to which additional responsibilities can be added.
Decorator - Maintains a reference to a Component object and defines an interface that conforms to Component's interface.
Concrete Decorators - Concrete Decorators extend the functionality of the component by adding state or adding behavior.
3. Implementation with Example
Let's take Starbuzz Beverages example from Head First Design Pattern Book. In this example, we will be decorating our Beverages using the Decorator Pattern.
1. Coffees
Dark Roast $0.99
Decaf $1.05
Espresso $1.99
House Blend $0.89
2. Condiments
Mocha $0.20
Steamed Milk $0.10
Soy $0.15
Whip $0.10
Let's draw a class diagram for Starbuzz Beverages Example.
For example, if the customer wants a Dark Roast with Mocha and Whip, then we’ll:
- Take a DarkRoast object
- Decorate it with a Mocha object
- Decorate it with a Whip object
- Call the cost() method and rely on a delegation to add to the condiment costs It's time to write step by step source code to implement Ordering System for Coffee House.
Step 1: Let's create Beverage is an abstract class with the two methods getDescription() and cost().
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
getDescription() method is already implemented for us, but we need to implement cost() in the subclasses.
Step 2: Let’s implement the abstract class for the Condiments (Decorator) as well :
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
Now that we’ve got our base classes out of the way, let’s implement some beverages.
Step 3 : let’s implement some beverages.
First, we extend the Beverage class since this is a beverage.
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
public double cost() {
return 1.99;
}
}
To take care of the description, we set this in the constructor for the class. Remember the description instance variable is inherited from Beverage.
Finally, we need to compute the cost of an Espresso. We don’t need to worry about adding in condiments in this class, we just need to return the price of an Espresso: $1.99.
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}
public double cost() {
return .89;
}
}
Okay, here’s another Beverage. All we do is set the appropriate description, “House Blend Coffee,” and then return the correct cost: 89¢.
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "Dark Roast Coffee";
}
public double cost() {
return .99;
}
}
public class Decaf extends Beverage {
public Decaf() {
description = "Decaf Coffee";
}
public double cost() {
return 1.05;
}
}
Coding condiments
If you look back at the Decorator Pattern class diagram, you’ll see we’ve now written our abstract component (Beverage), we have our concrete components (HouseBlend), and we have our abstract decorator (CondimentDecorator). Now it’s time to implement the concrete decorators. Here’s Mocha:
Mocha is a decorator, so we extend CondimentDecorator
We’re going to instantiate Mocha with a reference to a Beverage using:
(1) An instance variable to hold the beverage we are wrapping.
(2) A way to set this instance variable to the object we are wrapping.
Here, we’re going to pass the beverage we’re wrapping to the decorator’s constructor.
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
public double cost() {
return .20 + beverage.cost();
}
}
Now we need to compute the cost of our beverage with Mocha. First, we delegate the call to the object we’re decorating, so that it can compute the cost; then, we add the cost of Mocha to the result.
Similarly, let's create other condiments:
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
public double cost() {
return .15 + beverage.cost();
}
}
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
public double cost() {
return .10 + beverage.cost();
}
}
public class Milk extends CondimentDecorator {
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
public double cost() {
return .10 + beverage.cost();
}
}
Let's write a code to serve some coffees Here’s some test code to make orders:
- Order up an espresso, no condiments and print its description and cost.
public class StarbuzzCoffee {
public static void main(String args[]) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()
+ " $" + beverage.cost());
// Make a DarkRoast object
Beverage beverage2 = new DarkRoast();
// Wrap it with a Mocha.
beverage2 = new Mocha(beverage2);
// Wrap it in a second Mocha
beverage2 = new Mocha(beverage2);
// Wrap it in a Whip
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()
+ " $" + beverage3.cost());
}
}
Real World Decorators: Java I/O
A large number of classes in the java.io package is... overwhelming. Don’t feel alone if you said “whoa” the first (and second and third) time you looked at this API. But now that you know the Decorator Pattern, the I/O classes should make more sense since the java.io package is largely based on Decorator. Here’s a typical set of objects that use decorators to add functionality to reading data from a file:
Writing your own Java I/O Decorator
First, extend the FilterInputStream, the abstract decorator for all InputStreams.
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = super.read(b, offset, len);
for (int i = offset; i < offset+result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
Write some quick code to test the I/O decorator:
public class InputTest {
public static void main(String[] args) throws IOException {
int c;
try {
InputStream in =
new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")));
while((c = in.read()) >= 0) {
System.out.print((char)c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4. Conclusion
In this post, we have learned the Decorator Pattern from Head First Design Patterns book. There is a separate post for Factory Pattern in detail with examples, advantages, real-world examples.
All the source code for this post available on Github Repo. This is eclipse project so you can import into your workspace and play with it.
There more examples available on Github Repository:
Read more about Factory Pattern on:
5. Reference
https://ramesh-java-design-patterns.blogspot.com/2017/12/decorator-design-pattern.html
6. Top Java Tutorials
- Java Tutorial for Beginners
- 50 Java Keywords
- JDBC 4.2 Tutorial
- All Java/J2EE Tutorial
- Java 8 Tutorial
- Java Collections Tutorial
- Java Exceptions Tutorial
- Java Generics Tutorial
- Java 8 Stream API Tutorial
- Java Wrapper Classes
- Java Arrays Guide
- Java Multithreading Tutorial
- Java Concurrency Tutorial
- Oops Concepts Tutorial
- Java String API Guide
- Java Reflection API Tutorial
- Java I/O Tutorial
- Date and Time API Tutorial
- JUnit 5 Tutorial
- JUnit 4 Tutorial
- Java XML Tutorial
- Google GSON Tutorial
Comments
Post a Comment
Leave Comment