Decorator design pattern allows us to dynamically add functionality and behavior to an object without affecting the behavior of other existing objects within the same class. We use inheritance to extend the behavior of the class. This takes place at compile-time, and all the instances of that class get the extended behavior.
- Decorator patterns allow a user to add new functionality to an existing object without altering its structure. So, there is no change to the original class.
- The decorator design pattern is a structural pattern, which provides a wrapper to the existing class.
- The decorator design pattern uses abstract classes or interfaces with the composition to implement the wrapper.
- Decorator design patterns create decorator classes, which wrap the original class and supply additional functionality by keeping the class methods’ signature unchanged.
- Decorator design patterns are most frequently used for applying single responsibility principles since we divide the functionality into classes with unique areas of concern.
- The decorator design pattern is structurally almost like the chain of responsibility pattern.
Remember: Certain key points are to be taken into consideration that are as follows:
- Decorator design pattern is useful in providing runtime modification abilities and hence more flexible. Its easy to maintain and extend when the amount of choices are more.
- The disadvantage of decorator design pattern is that it uses plenty of similar kind of objects (decorators)
- Decorator pattern is used a lot in Java IO classes, like FileReader, BufferedReader, etc.
Procedure:
- Create an interface.
- Create concrete classes implementing the same interface.
- Create an abstract decorator class implementing the above same interface.
- Create a concrete decorator class extending the above abstract decorator class.
- Now use the concrete decorator class created above to decorate interface objects.
- Lastly, verify the output
Implementation:
We’re going to create a Shape interface and concrete classes implementing the Shape interface. We will then create an abstract decorator class ShapeDecorator implementing the Shape interface and having the Shape object as its instance variable.
- ‘Shape’ is the name of the interface
- ‘Rectangle’ class and ‘Circle’ class will be concrete classes implementing the ‘Shape’ interface.
- ‘ShapeDecorator’ is our abstract decorator class implementing the same ‘Shape’ interface.
- RedShapeDecorator is a concrete class implementing ShapeDecorator.
- DecoratorPatternDemo, our demo class will use RedShapeDecorator to decorate Shape objects.
Step 1: Creating an interface named ‘Shape’
Example
Java
// Interface named Shape public interface Shape { // Method inside interface void draw(); } |
Step 2: Create concrete classes implementing the same interface. Rectangle.java and Circle.java are as follows
Example
Java
// Class 1 // Class 1 will be implementing the Shape interface // Rectangle.java public class Rectangle implements Shape { // Overriding the method @Override public void draw() { // /Print statement to execute when // draw() method of this class is called // later on in the main() method System.out.println( "Shape: Rectangle" ); } } |
Java
// Circle.java public class Circle implements Shape { @Override public void draw() { System.out.println( "Shape: Circle" ); } } |
Step 3: Create an abstract decorator class implementing the Shape interface.
Example
Java
// Class 2 // Abstract class // ShapeDecorator.java public abstract class ShapeDecorator implements Shape { // Protected variable protected Shape decoratedShape; // Method 1 // Abstract class method public ShapeDecorator(Shape decoratedShape) { // This keywordd refers to current object itself this .decoratedShape = decoratedShape; } // Method 2 - draw() // Outside abstract class public void draw() { decoratedShape.draw(); } } |
Step 4: Create a concrete decorator class extending the ShapeDecorator class.
Example
Java
// Class 3 // Concrete class extending the abstract class // RedShapeDecorator.java public class RedShapeDecorator extends ShapeDecorator { public RedShapeDecorator(Shape decoratedShape) { super (decoratedShape); } @Override public void draw() { decoratedShape.draw(); setRedBorder(decoratedShape); } private void setRedBorder(Shape decoratedShape) { // Display message whenever function is called System.out.println( "Border Color: Red" ); } } |
Step 5: Using the RedShapeDecorator to decorate Shape objects.
Example
Java
// DecoratorPatternDemo.java // Class // Main class public class DecoratorPatternDemo { // Main driver method public static void main(String[] args) { // Creating an object of Shape interface // inside the main() method Shape circle = new Circle(); Shape redCircle = new RedShapeDecorator( new Circle()); Shape redRectangle = new RedShapeDecorator( new Rectangle()); // Display message System.out.println( "Circle with normal border" ); // Calling the draw method over the // object calls as created in // above classes // Call 1 circle.draw(); // Display message System.out.println( "\nCircle of red border" ); // Call 2 redCircle.draw(); // Display message System.out.println( "\nRectangle of red border" ); // Call 3 redRectangle.draw(); } } |
Step 6: Verifying the output
Output:
Circle with normal border Shape: Circle Circle of red border Shape: Circle Border Color: Red Rectangle of red border Shape: Rectangle Border Color: Red
Output explanation:
Glancing at the decorator design pattern one can conclude out that this is often a decent choice in the following cases where
- When we wish to add, enhance or perhaps remove the behavior or state of objects.
- When we just want to modify the functionality of a single object of the class and leave others unchanged.