The Decorator pattern: an easy way to add functionality

Photo by Thomas balabaud: https://www.pexels.com/photo/framed-photo-lot-1579708/

Introduction

The Decorator pattern can be used to dynamically alter or add functionality to existing classes. This pattern is oftern more efficient than subclassing because it relieves us of the need of defining a new object.

So, what does it look like?

To decorate a certain class the following steps must be taken:

  1. Construct a subclass, or in our case an implementation of the interface you want to decorate.
  2. In the Decorator class, make sure you add a reference to the original class.
  3. Also in constructor of the Decorator class, make sure to pass a reference to the original class to the constructor.
  4. Where needed, forward all requests to methods in the original class.
  5. And where needed, change the behaviour of the rest.

This all works because both the Decorator and the original class implement the same interface.

Implementation in Rust

Open your terminal or commandline in an empty directory and type:

cargo new rust_decorator
cd rust_decorator

Now open your favourite IDE and in the src directory open the main.rs file.

We will start with the Component trait:

trait Component {
    fn operation(&self) -> String;
}

For the sake of simplicity this component only has one method.

Now we need to implement this trait:

struct ConcreteComponent;

impl Component for ConcreteComponent {
    fn operation(&self) -> String {
        String::from("ConcreteComponent")
    }
}

Again, very simple, the operation() method just returns a fixed string.

Now it is time to define a Decorator struct with its implementation:

struct Decorator<T: Component> {
    component: T,
}

impl<T: Component> Decorator<T> {
    fn new(component: T) -> Self {
        Decorator { component }
    }
}

impl<T: Component> Component for Decorator<T> {
    fn operation(&self) -> String {
        format!("Decorator({})", self.component.operation())
    }
}

A few notes:

  1. As mentioned in the introduction, a decorator wraps the object it wants to decorate.
  2. There is also a constructor that takes the decorated class as an argument.
  3. The decorated class operation() method is called within the Decorator’s operation() method.

And now, a simple test:

fn main() {
    let component = ConcreteComponent;
    let decorator = Decorator::new(component);

    println!("{}", decorator.operation());
}

A breakdown, line by line:

  1. A ConcreteComponent is made.
  2. This is wrapped in a Decorator
  3. Now we do not call the operation() method on the ConcreteComponent but on the Decorator, made possible by the fact that both implement the same trait.

Conclusion

As you can see, the Decorator pattern is an easy way to either dynamically or statically change or add behaviour to objects. In many cases it is more flexible than subclassing. Go, of course doesn’t have subclasses in the strict OOP sense.

Use cases could include for example be adding behaviour to flyweight objects or logging.

Leave a Reply

Your email address will not be published. Required fields are marked *