You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现适配烘焙模拟库的泛型/trait参数Builder模式API?

实现思路与代码示例

1. 定义原料Trait与原料结构体

先给所有原料定义统一的trait,既方便Builder统一处理,也能满足后续求和的需求:

use std::ops::Add;
use chrono::{Date, Utc, Duration}; // 用chrono处理日期,可替换为其他日期库实现

// 原料核心Trait,包含基础能力与求和接口
trait Ingredient: Clone + 'static {
    // 获取原料数量
    fn amount(&self) -> f32;
    // 同类型原料相加的逻辑
    fn add(self, other: Self) -> Self;
}

// 为Add trait提供默认实现(针对同类型原料)
impl<T: Ingredient> Add for T {
    type Output = T;
    fn add(self, rhs: Self) -> Self::Output {
        self.add(rhs)
    }
}

// 定义具体原料并实现Ingredient
#[derive(Clone)]
struct Sugar(f32);
impl Ingredient for Sugar {
    fn amount(&self) -> f32 { self.0 }
    fn add(self, other: Self) -> Self { Sugar(self.0 + other.0) }
}

#[derive(Clone)]
struct Chocolate(f32);
impl Ingredient for Chocolate {
    fn amount(&self) -> f32 { self.0 }
    fn add(self, other: Self) -> Self { Chocolate(self.0 + other.0) }
}

#[derive(Clone)]
struct Vanilla(f32);
impl Ingredient for Vanilla {
    fn amount(&self) -> f32 { self.0 }
    fn add(self, other: Self) -> Self { Vanilla(self.0 + other.0) }
}

2. 实现RecipeBuilder与Recipe

RecipeBuilder需要支持添加任意数量、任意类型的原料,这里用trait object来存储不同类型的原料实例:

// 最终的Recipe结构体,持有所有原料的集合
struct Recipe {
    ingredients: Vec<Box<dyn Ingredient>>,
}

// RecipeBuilder实现,默认初始化空的原料列表
#[derive(Default)]
struct RecipeBuilder {
    ingredients: Vec<Box<dyn Ingredient>>,
}

impl RecipeBuilder {
    // 接收任意实现Ingredient的类型,装箱后加入列表
    fn add<I: Ingredient>(mut self, ingredient: I) -> Self {
        self.ingredients.push(Box::new(ingredient));
        self
    }

    // 构建并返回Recipe实例
    fn build(self) -> Recipe {
        Recipe { ingredients: self.ingredients }
    }
}

3. 实现AllocationBuilder与Allocation

AllocationBuilder负责绑定食谱、设置时间参数,最终生成规划实例:

// 最终的Allocation结构体,存储食谱与时间规划信息
struct Allocation {
    recipe: Recipe,
    start_date: Date<Utc>,
    duration_weeks: u32,
}

// AllocationBuilder实现,默认初始化空的参数状态
#[derive(Default)]
struct AllocationBuilder {
    recipe: Option<Recipe>,
    start_date: Option<Date<Utc>>,
    duration_weeks: Option<u32>,
}

impl AllocationBuilder {
    // 设置关联的食谱
    fn recipe(mut self, recipe: Recipe) -> Self {
        self.recipe = Some(recipe);
        self
    }

    // 设置持续周数
    fn duration_weeks(mut self, weeks: u32) -> Self {
        self.duration_weeks = Some(weeks);
        self
    }

    // 设置开始日期
    fn start_date(mut self, date: &Date<Utc>) -> Self {
        self.start_date = Some(*date);
        self
    }

    // 构建Allocation,这里加入参数校验,确保必填项都已设置
    fn build(self) -> Result<Allocation, &'static str> {
        Ok(Allocation {
            recipe: self.recipe.ok_or("必须设置食谱")?,
            start_date: self.start_date.ok_or("必须设置开始日期")?,
            duration_weeks: self.duration_weeks.ok_or("必须设置持续周数")?,
        })
    }
}

4. 实际使用示例

现在完全可以按照你预期的API结构来调用:

fn main() {
    let today = Utc::today();

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

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

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

    let p2 = AllocationBuilder::default()
        .recipe(vanilla_sponge)
        .duration_weeks(2)
        .start_date(&(today + Duration::weeks(2)))
        .build()
        .unwrap();
}

额外补充

  • 如果需要按原料类型统计总用量,可以把Recipe里的Vec改成HashMap<TypeId, Vec<Box<dyn Ingredient>>>,方便按类型检索和求和。
  • build方法返回Result是为了处理必填项缺失的情况,你也可以根据需求改成直接panic或者其他容错逻辑。

内容的提问来源于stack exchange,提问作者mmmoli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 20:33:22