Home > Enterprise >  How do I implement this API? It's a Builder pattern that takes generic (or trait) params
How do I implement this API? It's a Builder pattern that takes generic (or trait) params

Time:08-17

Scenario

Imagine you're a baker. You bake different cakes using different recipes.

Goal

You're trying to plan how many ingredients you'll need over the next 6m using your upcoming list of orders.

The challenge is, your orders aren't certain. They're based on a probability.

Problem

I've started hallo – a library to run these kind of simulations. I'm stuck on how to implement the ergonomics of the API I've imagined. Here's what I have in mind:

struct Sugar(f32);
struct Chocolate(f32);
struct Vanilla(f32);

let brownieRecipe = RecipeBuilder::default()
    .add(Sugar(200.0))
    .add(Chocolate(10.0))
    .build();

let vanillaSponge = RecipeBuilder::default()
    .add(Sugar(300.0))
    .add(Vanilla(2.0))
    .build();

let p1 = AllocationBuilder::default()
    .recipe(brownieRecipe)
    .duration_weeks(5)
    .start_date(&(today   Duration::weeks(8)))
    .build();

let p2 = AllocationBuilder::default()
    .recipe(vanillaSponge)
    .duration_weeks(2)
    .start_date(&(today   Duration::weeks(2)))
    .build();

The RecipeBuilder builds a recipe from any number of ingredients. The AllocationBuilder takes a recipe and plans when it's needed.

The next step is to implement a simulation engine to plot 10k outcomes (but that's out of scope of this question).

The ingredient structs might eventually implement some trait so that they can be summed (std::Add?).

CodePudding user response:

You'll need to have some trait for using the ingredients. What trait - that depends on what you need to do with them. It can be a builtin trait or a custom trait, but it is important that all ingredients (Sugar, Chocolate etc.) will implement it (I'd go with a custom trait).

Assuming you have a trait Ingredient, store a list of Vec<Box<dyn Ingredient>>. For the API to be what you described, you need the add() method to be defined like:

pub fn add<I: Ingredient>(&mut self, ingredient: I) {
    self.ingredients.push(Box::new(ingredient));
}

This is a convenience method: instead of needing to pass Box<dyn Ingredient>, you pass impl Ingredient and the method creates the Box<dyn Ingredient>. This is a very common pattern in Rust.

CodePudding user response:

Combining details from @chayim-friedman and @blackbeans, we've got it!

pub fn add<I: Ingredient>(&mut self, ingredient: I) -> Self {
    self.ingredients.push(Box::new(ingredient));
    self
}
  • Related