Using the decorator pattern to add logic to an interface without altering its existing behaviour
Photo by Markus Spiske on Unsplash

Using the decorator pattern to add logic to an interface without altering its existing behaviour

Suppose you have an interface that is used across different classes. You want to add some behaviour to that interface when it is used in a class and keep the behaviour unchanged for the other classes. The decorator pattern could come in handy.

public interface Question {
    void ask();
}

class QuestionPool {
    private final List<Question> questions;

    public QuestionPool(List<Question> questions) {
        this.questions = questions;
    }

    Question getQuestion(int index) {
        return questions.get(index);
    }
}

In this particular scenario I want to add functionality to the Question#ask() method so that an event is emitted. The event should only be emitted when the last question is asked, however I want to keep the behaviour unchanged in other instances. This means I cannot directly edit the Question implementation. The decorator pattern is one possible solution, so where to start?

public abstract class QuestionDecorator implements Question {

    protected final Question question;

    protected QuestionDecorator(Question question) {
        this.question = question;
    }

    public void ask() {
        this.question.ask();
        this.decorate();
    }

    public abstract void decorate();
}

First I am creating an abstract class named QuestionDecorator that implements the Question interface. The constructor takes in a question and the class implements the ask method from the Question interface. The implementation consists in invoking the original behaviour from the question object, then invoking the abstract method named decorate. The idea from now on is that I can extend the QuestionDecorator and add the personalised decoration logic in the child class. Additionally I am keeping the question field has protected to allow accessibility by child classes.

My next step is implementing the FinalQuestionDecorator class:

public class FinalQuestionDecorator extends QuestionDecorator {

    public FinalQuestionDecorator(Question question) {
        super(question);
    }

    @Override
    public void decorate() {
        emitEvent(super.question);
    }

    public void emitEvent(Question question) {
        // emit the event logic here...
    }
}

The FinalQuestionDecorator class responsibility is simply to add the decorated behaviour. In this particular case it will emit an event with that question. So when a question is an instance of a FinalQuestionDecorator class, by invoking the ask method I will be triggering the decorated behaviour.

All is left do to is to use the decorated class when required:

class QuestionPool {
    private final List<Question> questions;

    public QuestionPool(List<Question> questions) {
        this.questions = questions;
    }

    Question getQuestion(int index) {
        if (index == questions.size() - 1) {
            return new FinalQuestionDecorator(questions.get(index));
        }
        return questions.get(index);
    }
}

Suppose the last question is identified as the last Question object in the questions list, then the QuestionPool#getQuestion(int index) method is updated so that the last question in the list is decorated with the FinalQuestionDecorator class.