Codementor Events

The Decorator pattern: an easy way to add functionality

Published Jun 03, 2023

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?

decorator.drawio (1).png

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.

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.

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.

Discover and read more posts from Iede Snoek
get started