如何实现适配烘焙模拟库的泛型/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
相关产品推荐
相关产品推荐

