Design-Pattern -- Decorator Pattern

1. Definition:

Let us first look at the strict definition of Decorator Pattern,

Decorator Pattern attaches additional responsibilities to one Object dynamically, without affecting the behaviour of other objects from the same class. Decorator provides a flexible alternative to sub-classes for extending functionalities. (Wiki_Link_For_More_Info)


Detail of the definition:

  • dynamically : The meaning at dynamically means that those attached responsibilities are not *hard-coded in to the object class _at compile time_. Instead, these responsibilities are added _at runtime time_ by different Decorator. At the same time, one type of object could have multiple types of decorators, so a client can choose any one or any combination of them to decorate the object at runtime.
  • flexible alternative: decorator pattern using _Composition_ rather than _Inheritance_. Since inheritance is not used, the original class functionalities will not be changed.

Before going to the UML of decorator pattern, let us look at the design principles it bases on.

  1. Open Close Principle: Software entities like classes, modules and functions should be _open for extension_ but _closed for modifications_. When the requirements have been changed for one class, it is not recommended to go into that class and change the code, instead, using abstract class and concrete classes for implementing their behaviour. In different scenario, using different concrete classes. In decorator pattern, Component are usually interface or abstract class.

  2. Single responsibilities Principle: A class should have _only one reason_ to change. This means that if we have 2 reasons to change for a class, we have to split the functionality in two classes. In decorator pattern, each concrete decorator has only changed one responsibility of the original class. In order to get multiple responsibilities changed, using composition of decorators.

Now, let us draw a picture to see what a decorator and component relation actually is.

Smiley face
decorator and Component relation

Notice

  1. Decorator _has-a_ component of that type. Decorator is a wrapper of component. It has a reference or pointer to the component object so that it can invoke the method defines in that component.
  2. Meanwhile, Decorator _is-a_ component. Decorator itself must also be a component of that type. So, a decorator can be viewed as a component and wrapped by another Decorator. (Very similar to recursion.)

2. General UML:

Smiley face
UML for decorator pattern

Here, I implement the UML as an example with slightly differences interface Component:

public class Component implements IComponent {
    @Override
    public void operation() {
        System.out.println("I am a base method in concrete Component");
    }
}

Abstract Class decorator:

public abstract class Decorator implements IComponent{// it is a component

    private IComponent component;// it has-a component

    public Decorator(IComponent component){
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();// invoke base method
    }
}

Concrete decorator A:

public class DecoratorA extends Decorator {

    public DecoratorA(IComponent component) {
        super(component);
    }

    private void preOperation() {
        System.out.println("this is a pre-operation defined in decoratorA");
    }

    /*
     * Add a pre-operation before base Operation
     * */
    @Override
    public void operation() {
        this.preOperation();
        super.operation();
    }
}

Concrete decorator B:

public class DecoratorB extends Decorator {

    //decorator can have new properties and method.
    private String newSate = "I am a new State";

    public DecoratorB(IComponent component) {
        super(component);
    }

    private void postOperation(){
        System.out.println("this is a post-operation defined in decoratorB");
    }

    @Override
    public void operation() {
        super.operation();
        this.postOperation();
    }

    public void setNewSate(String newSate) {
        this.newSate = newSate;
    }

    public String getNewSate() {
        return newSate;
    }
}

Test Main_function:

public class Main {

    public static void main(String[] args) {

        IComponent component = new Component();
        component.operation();
        System.out.println("------------------------------");

        IComponent concreteDecortorA = new DecoratorA(component);
        concreteDecortorA.operation();// operation after decorating by A

        System.out.println("------------------------------");
        IComponent concrteDecortorB = new DecoratorB(component);
        concrteDecortorB.operation();// operation after decorating by B

        System.out.println("------------------------------");
        // decorator of decorator, by passing concreteDecortorA as parameter of decoratorB
        IComponent compositeDecortor = new DecoratorB(concreteDecortorA);
        // first invoke operation defined by concreteDecortorA, concreteDecortorA will invoke the operation defined by the
        // base component
        compositeDecortor.operation();
    }
}

Output:

I am a base method in concrete Component
------------------------------
this is a pre-operation defined in decoratorA
I am a base method in concrete Component
------------------------------
I am a base method in concrete Component
this is a post-operation defined in decoratorB
------------------------------
this is a pre-operation defined in decoratorA
I am a base method in concrete Component
this is a post-operation defined in decoratorB

Comments: For composite decorator, it takes another decorator as a component, and when it invoke the operation() method, it will invoke the operation defined by the component(which is also a decorator) , and that component recursively invoke the base component(which is actually a real component), it invokes the base method ,and returns the result to its decorator, and that decorator returns the result to the composite decorator.

3. Java IO - InputStream:

java.io.inputStream uses the decorator Pattern. From the book Head First Design Patterns,we can see how they use decorator Pattern in java to add functionalities for reading data from files.

Smiley face
how decorator pattern used in java.io
Smiley face
java.io.inputStream hierarchy

The usage of decorator pattern in java.io also points to the _drawback_ of decorator pattern. There are _so many decorators implementation classes_. It can be confusing for clients who call those decorator-API. It is somehow hard when those clients can not tell the difference between each decorators. However, once they know, they can combine different decorators for their own applications.

Now, let us create a own type of Input Stream and test it.

import java.io.*;

public class UpperCaseInputStream extends FileInputStream {// is-a
    // Constructors which matches the super class FileInputStream
    private FileInputStream fileInputStream; //has-a


    public UpperCaseInputStream(String name,FileInputStream fileInputStream) throws FileNotFoundException {
        super(name);
        this.fileInputStream = fileInputStream;
    }


    public UpperCaseInputStream(File file,FileInputStream fileInputStream) throws FileNotFoundException {
        super(file);
        this.fileInputStream = fileInputStream;
    }

    public UpperCaseInputStream(FileDescriptor fdObj,FileInputStream fileInputStream) {
        super(fdObj);
        this.fileInputStream = fileInputStream;
    }

    @Override
    public int read() throws IOException {
        int nextByte = super.read();
        if (nextByte == -1) return -1;
        return Character.toUpperCase((char) (nextByte));
    }

    @Override
    public int read(byte[] b) throws IOException {
        int totalBytes = super.read(b);
        if (totalBytes == -1) return -1;
        convertToUppercase(b, 0, totalBytes);
        return totalBytes;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int totalBytes = super.read(b, off, len);
        if (totalBytes == -1) return -1;
        // convert all the character in byte array to uppercase letter
        convertToUppercase(b, off, off + totalBytes);
        return totalBytes;
    }

    private static void convertToUppercase(byte[] b, int start, int end) {
        for (int idx = start; idx < end; idx++) {
            b[idx] = (byte) Character.toUpperCase((char) (b[idx]));
        }
    }
}

Test our UpperCaseInputStream:

import java.io.*;

public class UpperCaseInputStreamTest {

    public static void main(String[] args) {
        String filePath = "data/text_file.txt";
        FileInputStream fileinputStream = null;
        UpperCaseInputStream upperCaseInputStream = null;
        try {
            // 1\. Create a component with one base class: FileInputStream
            fileinputStream = new FileInputStream(filePath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        try {
            //2\. Create our customized decorator with the base component
            upperCaseInputStream = new UpperCaseInputStream(filePath, fileinputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        //3.1Test read once at a time
        /*try {
            do{
                System.out.print((char) upperCaseInputStream.read());
            }while (upperCaseInputStream.read() != -1);
        } catch (IOException e) {
            e.printStackTrace();
        }*/

        //3.2 Test read into byte array buffer
        byte[] buf = new byte[1024];
        int length;
        try {
            while ((length = upperCaseInputStream.read(buf)) != -1) {
                System.out.println(new String(buf, 0, length));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //4\. release the resources
        try {
            upperCaseInputStream.close();
            fileinputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

OutPut:

BCDEFGHIJKLMN
GKIERERU

text_file.txt :

bcdefghijklmn
GKiereru

Hence, our UpperCaseInputStream meets the expectation.


Author: Liang Tan
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Liang Tan !
  TOC