Decorator
Motivation
Suppose that we are working on a user interface toolkit and we want to be able to add borders and scroll bars to the windows. If we use inheritance, we will extend Window class with new classes, such as WindowVerticalScrollBar, WindowHorizontalScrollBar, WindowBorder, etc.
The solution with the inheritance is not flexible, since we will end up with too many subclasses. Such a hierarchy is difficult to maintain, difficult to extend and difficult to use.
But, if we enclose a window in an object which can add new features, such as scroll bar and border dynamically, we will have a much more flexible solution. The “enclosed” object is a decorator.
The Decorator pattern attaches additional responsibilities to an object dynamically.
Story
The spoilers that are added to a car are examples of the Decorator. The spoilers do not change the car itself, but add additional functionality which is to ‘spoil’ unfavorable air movement across a body of a vehicle in motion, usually described as turbulence or drag.
Image
2013 Porsche 911 Carrera S (8233337583), by steve lyon from los angeles, ca, usa,CC BY-SA 2.0
UML
Structure
The Component defines interfaces for new features which will be added dynamically.
The ConcreteComponent class defines object where new features can be added.
The Decorator abstract class holds reference to the Component object.
The ConcreteDecorator class adds new features to the Component object.
Implementation
Component.java
package com.hundredwordsgof.decorator;
/**
*
* Component, defines interface for new features which will be added dynamicaly
*
*/
public interface Component {
void operation();
}
ConcreteComponent.java
package com.hundredwordsgof.decorator;
/**
*
* ConcreteComponent, define object where new features can be added
*
*/
public class ConcreteComponent implements Component {
public void operation() {
}
}
Decorator.java
package com.hundredwordsgof.decorator;
/**
*
* Decorator, keep reference to Component object
*
*/
abstract class Decorator implements Component {
protected Component component;
public abstract void operation();
public void setComponent(Component component) {
this.component = component;
}
}
ConcreteDecoratorA.java
package com.hundredwordsgof.decorator;
/**
*
* ConcreteDecoratorA, add features to component
*
*/
public class ConcreteDecoratorA extends Decorator {
private boolean state;
public void operation() {
state = true;
this.component.operation();
}
public boolean isState() {
return state;
}
}
ConcreteDecoratorB.java
package com.hundredwordsgof.decorator;
/**
*
* ConcreteDecoratorB, add features to component
*
*/
public class ConcreteDecoratorB extends Decorator {
private boolean behaviorMethodInvoked = false;
public void operation() {
this.component.operation();
addedBehavior();
}
private void addedBehavior() {
behaviorMethodInvoked = true;
}
protected boolean isBehaviorMethodInvoked() {
return behaviorMethodInvoked;
}
}
Usage
DecoratorTest.java
package com.hundredwordsgof.decorator;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
/**
* Test Decorator pattern.
*/
public class DecoratorTest {
@Test
public void testDecorator() {
Component component = new ConcreteComponent();
Decorator decoratorA = new ConcreteDecoratorA();
decoratorA.setComponent(component);
decoratorA.operation();
assertEquals(true, ((ConcreteDecoratorA) decoratorA).isState());
Decorator decoratorB = new ConcreteDecoratorB();
decoratorB.setComponent(component);
assertEquals(false,
((ConcreteDecoratorB) decoratorB).isBehaviorMethodInvoked());
decoratorB.operation();
assertEquals(true,
((ConcreteDecoratorB) decoratorB).isBehaviorMethodInvoked());
}
}