Codementor Events

Design Patterns in Rust: The Template Method made easy

Published Apr 03, 2023

The template method pattern is a behavourial design pattern which allows you to define the template, or the skeleton of an operation in a base class, while allowing the subclasses to override specific steps or all steps of the algorithm without changing its structure.

This pattern is very useful when you have a series of steps that needs to be performed in a specific order, but you need different implementations in different situations.

So, what does it look like? Well, like this:
template_pattern.drawio.png

A short explanation:

  1. The Producer is tasked with producing some product, which in this case can be a _Car _or a Bike. In order to produce these products, two steps are needed.
  2. _BikeProducer _and CarProducer are the two concrete _Producer_s.
  3. The Client class both the _step1 _and the _step2 _method to obtain a product.

Since Rust does not support the notion of superclasses or abstract classes, we can implement this with traits.

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

cargo new rust_template_pattern
cd rust_template_pattern

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

First we will define the trait:

trait Template {
    fn add_frame(&self);
    fn add_wheels(&self);
}

A vehicle in our world just consists of a frame and wheels, as you can see. Notice the &self arguments to both methods, so we can refer to the implementing struct when needed.

Now define the CarProducer:

struct CarProducer;

impl Template for CarProducer {
    fn add_frame(&self) {
        println!("Adding a car frame");
    }

    fn add_wheels(&self) {
        println!("Adding 4 wheels");
    }
}

A few notes:

  1. CarProducer is an empty struct as we do not need any data in the struct in this example.
  2. All the methods do is print out a message.

The BikeProducer is similar:

struct BikeProducer;

impl Template for BikeProducer {
    fn add_frame(&self) {
        println!("Adding a bike frame");
    }

    fn add_wheels(&self) {
        println!("Adding 2 wheels");
    }
}

Now we need the method to assemble it all:

fn produce_vehicle(producer:&dyn Template) {
    producer.add_frame();
    producer.add_wheels();
}

This method gets one parameter, a struct which implements the Template trait. Because we pass a trait, we use the dyn keyword.

Now it is time to test it all:

fn main() {
    let producer=BikeProducer;
    produce_vehicle(&producer);    
}

Line by line:

  1. We instatiate BikeProducer struct.
  2. We pass this to the produce_vehicle function, we can do this because BikeProducer implements the Template trait.

If you don’t want to have a parameter with dyn, or perhaps you think that generics are more readable, you can always implement the produce_vehicle function like this:

fn produce_vehicle<T:Template>(producer:&T) {
    producer.add_frame();
    producer.add_wheels();
}

To me this looks more readable, however you are free to choose either way of course, it all depends on your taste and programming style.

The template pattern is one of the easiest patterns to implement. It was quite straightforward to get it right in Rust, and I found that the use of generics can make things more readable.

Discover and read more posts from Iede Snoek
get started
post commentsBe the first to share your opinion
Show more replies