- Intro to Java Objects and Classes
- Intro to Java and Static Stuff
- Intro To Java Classes Revisited
- Intro to Java Interfaces
- Intro to Java Abstract Classes
- Intro to Java Reflection
- Intro to Java Collections
- Intro to Java Generics
- Intro to Java Annotations
- Intro to Java Patterns
- Intro to Java Adapter Pattern
- Intro to Java Singleton Pattern
- Intro to Java Decorator Pattern
We have looked at the Factory, the Adapter, and the Singleton patterns. Now we look at the Decorator. The Decorator is similar to the Adapter, but with a subtle difference. With our ReptileAdapter, we wrapped the Reptile class to map the interface methods of Animal to the appropriate methods on Reptile. With the Decorator we also wrap a class, but to add functionality, not to map it or replace the functionality.
The Decorator in the Physical World
The idea behind decorating things in the real world is pretty much how it sounds. We wrap an object with new functionality, while keeping the old functionality. How about a camera in a waterproof housing? When closed up, we have added the functionality of waterproofing, but we still provide a way to press the buttons and turn the dials so that the camera can be operated.
The Decorator in the Software World
On the software side, the idea is the same. It is usually used when we have one object that we like how it functions, but we want to augment the behavior for a different situation. Done correctly, we can keep inserting one object inside another adding functionality at every step.
What do we need to create a Decorator?
We need to create a class (our Decorator) that contains the class we want to decorate. We usually get this class into the Decorator using a constructor. Our Decorator needs to extend or implement the class or interface that contains the functionality we want to Decorate. Then when one of the methods of the decorated class is called, we call it before or after adding our own functionality.
Ummm, Example?
Lets use our Animals again. We are going to get a little bit emotional, or at least our animals are. Have you ever met people, who no matter what you ask them, has to tell you their emotional state? We will create an animal decorator that does just that. Every time we ask them to move() or speak() they will still move and speak, but they will also tell us their emotional state.
So, how do we accomplish this? Just as we could have done when we started looking at the Adapter, we could just go into the code for Animal or BaseAnimal and add new code to store a feeling, and put some getters and setters on it. But, our Animal code works, and not every animal likes to tell everyone they meet what they are feeling.
public abstract class EmotionDecorator extends BaseAnimal {
private Animal decoratedAnimal;
public EmotionDecorator(Animal pDecoratedAnimal) {
decoratedAnimal = pDecoratedAnimal;
}
public String getType() {
return decoratedAnimal.getType();
}
public void move() {
decoratedAnimal.move();
showEmotion();
}
public void speak() {
decoratedAnimal.speak();
showEmotion();
}
private void showEmotion() {
System.out.println("(Who by the way, is very " + getEmotion() + ".)");
}
public abstract String getEmotion();
}
So, we start by extending BaseAnimal. This means that we get all the functionality that is in BaseAnimal, and means that other objects can just treat us like an Animal. (BaseAnimal implements the Animal interface). We have a constructor that takes in an Animal to be decorated. Not every method is implemented by BaseAnimal however, so we provide the pass thru method for getType() and decorate move() and speak().
The getType() method does not add anything, but we still need to provide this pass thru method, because we want the decorated Animal’s type to be returned if someone asks us.
The move() and speak() methods get decorated. We first call the method on our decorated Animal, then we call showEmotion(). This does the same thing for both, it prints out how the animal is feeling. It gets its emotion from an abstract method. This allows us to push down the hierarchy to some small concrete classes just the specific behavior for each emotion. This does mean a different emotion class for each emotion, but it can be used for all animals. As an exercise, when this is over, try to create a EmotionDecorator that takes in a parameter for the emotion type instead.
public class HappyDecorator extends EmotionDecorator {
public HappyDecorator(Animal pDecoratedAnimal) {
super(pDecoratedAnimal);
}
public String getEmotion() {
return "happy";
}
}
Nothing to it. It just creates the constructor so our decorated animal is set up, and provides the correct emotion.
Lets see an example of how we construct this decorator now.
public static void main(String[] args) {
Animal aBird = new Bird();
Animal aHappyBird = new HappyDecorator(aBird);
aBird.move();
aHappyBird.move();
}
Here we create a Bird and we create a HappyDecorator that we put the bird into. If we call move() on aBird, we just get the move() method on bird. If we call the move() method on aHappyBird, we get the Bird.move() method called and the decoration.
We don't have to create and hold an instance of Bird. We could just add them by doing a new inside the constructor of our decorator. We can even chain our emotions together to get one confused bird.
public static void main(String[] args) {
Animal aAngryBird = new AngryDecorator(new Bird());
Animal aHappyBird = new HappyDecorator(new Bird());
Animal aConfusedBird = new AngryDecorator(new HappyDecorator(new Bird()));
aAngryBird.move();
aHappyBird.move();
aConfusedBird.move();
}
And just so you can see that we can decorate any of our Animals:
public static void main(String[] args) {
Animal aAngryBird = new AngryDecorator(new Bird());
Animal aHappyFish = new HappyDecorator(new Fish());
Animal aConfusedMammal = new AngryDecorator(new HappyDecorator(new Mammal()));
aAngryBird.move();
aHappyFish.move();
aConfusedMammal.move();
}
The chaining thing doesn't look all that useful in this context, but it can be a great tool. Lets take a quick look at another example, the MovingDecorator. This is almost the same implementation as the EmotionDecorator, but we are only going to decorate the move() method. Lets throw a new twist in there too. Often with a Decorator, there is some sort of setup or initialization to do, so lets allow a way to init this decorator with a value, and have an abstract method to provide the values expected type.
public abstract class MovingDecorator extends BaseAnimal {
private Animal decoratedAnimal;
private int movementAmount;
public MovingDecorator(Animal pDecoratedAnimal) {
decoratedAnimal = pDecoratedAnimal;
}
public void init(int pMovementAmount) {
movementAmount = pMovementAmount;
}
public String getType() {
return decoratedAnimal.getType();
}
public void move() {
decoratedAnimal.move();
StringWriter aWriter = new StringWriter();
aWriter.append("Move " + getMovement());
if (movementAmount != 0) {
aWriter.append(" " + movementAmount);
aWriter.append(" " + getMovementUnits());
}
aWriter.append(".");
System.out.println(aWriter.toString());
}
public void speak() {
decoratedAnimal.speak();
}
public abstract String getMovement();
public abstract String getMovementUnits();
}
We now have a more complicated move method, but the idea is much the same. Decorate the object, then add functionality.
Once again we need to create concrete methods for the movement types. We have MoveForward, MoveLeft, and MoveRight.
public class MoveRightDecorator extends MovingDecorator {
public MoveRightDecorator(Animal pDecoratedAnimal) {
super(pDecoratedAnimal);
}
public String getMovement() {
return "right";
}
public String getMovementUnits() {
return "degrees";
}
}
These look almost the same as our Emotion ones, but we also have to implement a movement units method.
For our purposes we didn't have to execute the init method, but we could. First is a simple class showing the using of this Decorator.
public static void main(String[] args) {
Animal aMammal = new MoveForwardDecorator(new Mammal());
aMammal.move();
}
Here shows chaining the move methods:
public static void main(String[] args) {
Animal aBird = new MoveForwardDecorator(new MoveLeftDecorator(
new MoveForwardDecorator(new MoveRightDecorator(new Fish()))));
aBird.move();
}
And finally, here we use the init method:
public static void main(String[] args) {
Animal aBird = new Bird();
MovingDecorator aDecoBird = new MoveForwardDecorator(aBird);
aDecoBird.init(10);
aDecoBird = new MoveRightDecorator(aDecoBird);
aDecoBird = new MoveForwardDecorator(aDecoBird);
aDecoBird = new MoveLeftDecorator(aDecoBird);
aDecoBird.init(90);
aDecoBird = new MoveForwardDecorator(aDecoBird);
//What happens when this is uncommented?
//aDecoBird = new HappyDecorator(aBird);
aDecoBird.move();
aDecoBird.speak();
}
If you noticed, I actually created a MovingDecorator instead of an Animal here, and that was simply so that I didn't have to keep casting to call the init method.
How about in the Java SDK?
One of the best examples is the FileIO section. This is the code that allows you to read and write to the file system. You start with a basic byte or character reader, then wrap with decorators that can read by lines, or buffer for more performance.
You can learn about file IO in this sun trail.
If you remember from the Reflection Session, we read data from a properties file. We will access a file the same way here. This is shown so you can see what it looks like with no decoration.
public class IODecoratorTest1 {
public static void main(String[] args) throws Exception {
int c;
InputStream aStream = IODecoratorTest1.class.getResourceAsStream("ioTest.txt");
while (( c = aStream.read() ) >= 0 ) {
System.out.println((char)c);
}
aStream.close();
}
}
IODecoratorTest2 shows how we decorate the input stream. First with InputStreamReader, then with BufferedReader, then finally adding a LineNumberReader.
Reader aReader = new InputStreamReader(IODecoratorTest1.class
.getResourceAsStream("ioTest.txt"));
Reader aReader = new BufferedReader(new InputStreamReader(
IODecoratorTest1.class.getResourceAsStream("ioTest.txt")));
Reader aReader = new LineNumberReader(new BufferedReader(
new InputStreamReader(IODecoratorTest2.class
.getResourceAsStream("ioTest.txt"))));
So, as you can see, we are layering, or stacking the classes. We are decorating the read() methods in each of these classes to add behavior. We can do the same thing. Lets write a Reader decorator ourselves that can count the number of times ‘I' is in the file.
public class ICountReader extends FilterReader {
private Reader inReader;
private int count;
public ICountReader(Reader pInReader) {
super(pInReader);
inReader = pInReader;
count = 0;
}
public int read() throws IOException {
int aByte = inReader.read();
if (aByte == 'I') {
count++;
}
return aByte;
}
public int read(char[] cbuf, int off, int len) throws IOException {
int n = inReader.read(cbuf, off, len);
for (int i = off; i < off + n; i++) {
int c = cbuf[i];
if (c == 'I') {
count++;
}
}
return n;
}
public int getICount() {
return count;
}
}
We are extending FilterReader instead of implementing the Reader interface so we don't have quite so many methods to implement. This is just the same as the other decorators we have been looking at. We can insert it into a decorator chain just the same way.
public class IODecoratorTest3 {
public static void main(String[] args) throws Exception {
int c;
ICountReader aICountReader = new ICountReader(new BufferedReader(new InputStreamReader(IODecoratorTest1.class.getResourceAsStream("ioTest.txt"))));
LineNumberReader aLineReader = new LineNumberReader(aICountReader);
while ((c = aLineReader.read()) >= 0) {
System.out.println((char) c);
}
aLineReader.close();
// We cast because we need a ICountRe to ask for the line
// number, and it is zero based so we add one
System.out.println("# of I's:" + aICountReader.getICount());
System.out.println("# of lines:" + (aLineReader.getLineNumber() + 1));
}
}