建造者模式
像2001年一样的配对
像2001年一样的配对
我们将帮助蛋糕店的约翰·麦克雷找到他梦中那个略带讽刺、又奇特又具体的女子。他是个特别的男人:
是的,你我都这么认为:这听起来像是 Rust 编译器的工作。这支乐队确实走在了时代的前列。让我们来模拟一下这个问题:
/// Girl type
struct Girl {}
impl Girl {
/// Construct a Girl
fn new() -> Self {
Self {}
}
}
/// Determine whether given girl matches spec
fn is_dream_girl(girl: &Girl) -> bool {
// we don't know anything about spec yet, so odds are no
false
}
fn main() {
let girl = Girl::new();
println!("Match: {}", is_dream_girl(&girl));
}
运行此程序cargo run
将产生预期的输出:Match: false
。
那么,我们具体要找的是什么呢?幸运的是,我们的男士一开始就告诉我们他的喜好,第一行就告诉我们他想要“一个头脑像钻石一样的女孩”。让我们添加一个会员字段来测试一下:
#[derive(Clone, Copy, PartialEq)]
enum Mind {
Computer,
Diamond,
Garden,
Scalpel,
Sieve,
Unknown,
}
struct Girl {
mind: Mind,
}
impl Girl {
fn new(mind: Mind) -> Self {
Self { mind }
}
}
现在测试函数可以检查所请求的变体:
fn is_dream_girl(girl: &Girl) -> bool {
girl.mind == Mind::Diamond
}
fn main() {
let girl = Girl::new(Mind::Diamond);
println!("Match: {}", is_dream_girl(&girl));
}
太棒了!现在我们Match: true
传入 this 时就得到了结果Girl
。不过等等——我们还有一些其他的条件。接下来,我们需要“一个知道什么是最好的女孩”。这很简单——她要么知道,要么不知道:
struct Girl {
mind: Mind,
knows_best: bool,
}
impl Girl {
fn new(mind: Mind, knows_best: bool) -> Self {
Self { mind, knows_best }
}
}
fn is_dream_girl(girl: &Girl) -> bool {
girl.mind == Mind::Diamond && girl.knows_best
}
只需将其添加到参数列表中:
fn main() {
let girl = Girl::new(Mind::Diamond, true);
println!("Match: {}", is_dream_girl(&girl));
}
太棒了!现在我们需要“鞋子会割伤,眼睛会像香烟一样燃烧”。听起来我们需要关联几对字符串:
type Attribute = (String, String);
/// Girl type
struct Girl {
items: Vec<Attribute>,
mind: Mind,
knows_best: bool,
}
属性将是一个类似 的元组("shoes", "cut")
。我们可以在构造函数中获取 shoes 和 eye 属性:
impl Girl {
fn new(mind: Mind, knows_best: bool, shoes: &str, eyes: &str) -> Self {
let mut ret = Self {
items: Vec::new(),
mind,
knows_best,
};
ret.push_item("shoes", shoes);
ret.push_item("eyes", eyes);
ret
}
fn push_item(&mut self, item_name: &str, attribute: &str) {
self.items.push((item_name.into(), attribute.into()));
}
}
我们将检查所有物品以确保我们得到了我们想要的东西:
fn is_dream_girl(girl: &Girl) -> bool {
let mut found_shoes = false;
let mut found_eyes = false;
for item in &girl.items {
if item.0 == "shoes" && item.1 == "cut" {
found_shoes = true;
} else if item.0 == "eyes" && item.1 == "burn like cigarettes" {
found_eyes = true;
}
}
girl.mind == Mind::Diamond && girl.knows_best && found_shoes && found_eyes
}
Girl
太棒了!我们只需要用新的属性来构造:
fn main() {
let girl = Girl::new(Mind::Diamond, true, "cut", "burn like cigarettes");
println!("Match: {}", is_dream_girl(&girl));
}
好的。稍等一下。你看出问题了吗?我们先快速浏览一下……
I want a girl with the right allocations
Who's fast and thorough
And sharp as a tack
She's playing with her jewelry
She's putting up her hair
She's touring the facility
And picking up slack
...
一切就从这里继续!这个Girl
构造函数已经失控了,我们才勉强完成了第一节。如果约翰改变主意怎么办?他可能会觉得有些事情不那么重要,或者添加一个新的条件。这段代码无法适应这样的变化,每个调用点都依赖于这个以特定顺序给出的参数列表,但人们不会这样做。可能会有各种各样的变化。
模式
让我们利用建造者模式重新实现这个程序。当 aGirl
首次构造时,我们只想从一些合理的默认值开始:
struct Girl {
items: Vec<Attribute>,
mind: Mind,
knows_best: bool,
}
impl Girl {
fn new() -> Self {
Self::default()
}
}
impl Default for Girl {
fn default() -> Self {
Self { mind: Mind::Unknown, knows_best: false, items: Vec::new() }
}
}
其他一切都是空白的。这样,我们就可以Girl::new()
不带参数直接使用,并得到一个起点。为了添加更多内容,我们可以定义方法:
impl Girl {
// ..
fn set_mind(&mut self, mind: Mind) -> &mut Self {
self.mind = mind;
self
}
}
此方法接受一个可变引用并返回一个,因此我们可以先构造然后再调整:
fn main() {
let mut girl = Girl::new();
girl.set_mind(Mind::Diamond);
println!("Match: {}", is_dream_girl(&girl));
}
让我们添加其余部分:
impl Girl {
fn push_item(&mut self, item_name: &str, attribute: &str) {
self.items.push((item_name.into(), attribute.into()));
}
fn toggle_knows_best(&mut self) -> &mut Self {
self.knows_best = !self.knows_best;
self
}
}
现在我们可以一次添加一个,而不管我们在构造时知道什么:
fn main() {
let mut girl = Girl::new();
girl.set_mind(Mind::Diamond);
girl.toggle_knows_best();
girl.push_item("shoes", "cut");
girl.push_item("eyes", "burn like cigarettes");
println!("Match: {}", is_dream_girl(&girl));
}
随着规范的增长和发展,这将变得更加容易使用。
更复杂的场景可能需要你使用单独的类型,例如 GirlBuilder,并在每一步都拥有所有权。这样你就可以在一行代码中完成所有操作:let girl = GirlBuilder::new().set_mind(Mind::Diamond).toggle_knows_best();
但这确实限制了你的配置选项,例如,如果你想在表达式中有条件地调用某个构建器方法if
。如果可能的话,这里的非拥有模式会更灵活。
希望我们能够帮助麦克雷先生在这么长时间之后最终安定下来。
挑战
做好准备,五年后这首歌就成为一首“老歌”了。
鏂囩珷鏉ユ簮锛�https://dev.to/decidously/the-builder-pattern-249l