默认值和建造者模式
你可以实现 Default
trait,给你认为最常见的 struct
或 enum
赋值。建造者模式可以很好地与之配合,让用户在需要时轻松地进行修改。首先我们来看看Default
。实际上,Rust中的大多数通用类型已经有Default
。它们并不奇怪。0, ""(空字符串), false
, 等等。
fn main() { let default_i8: i8 = Default::default(); let default_str: String = Default::default(); let default_bool: bool = Default::default(); println!("'{}', '{}', '{}'", default_i8, default_str, default_bool); }
这将打印'0', '', 'false'
。
所以Default
就像new
函数一样,除了你不需要输入任何东西。首先我们要创建一个struct
,它还没有实现Default
。它有一个new
函数,我们用它来创建一个名为Billy的角色,并提供一些统计信息。
struct Character { name: String, age: u8, height: u32, weight: u32, lifestate: LifeState, } enum LifeState { Alive, Dead, NeverAlive, Uncertain } impl Character { fn new(name: String, age: u8, height: u32, weight: u32, alive: bool) -> Self { Self { name, age, height, weight, lifestate: if alive { LifeState::Alive } else { LifeState::Dead }, } } } fn main() { let character_1 = Character::new("Billy".to_string(), 15, 170, 70, true); }
但也许在我们的世界里,我们希望大部分角色都叫比利,年龄15岁,身高170,体重70,还活着。我们可以实现Default
,这样我们就可以直接写Character::default()
。它看起来是这样的:
#[derive(Debug)] struct Character { name: String, age: u8, height: u32, weight: u32, lifestate: LifeState, } #[derive(Debug)] enum LifeState { Alive, Dead, NeverAlive, Uncertain, } impl Character { fn new(name: String, age: u8, height: u32, weight: u32, alive: bool) -> Self { Self { name, age, height, weight, lifestate: if alive { LifeState::Alive } else { LifeState::Dead }, } } } impl Default for Character { fn default() -> Self { Self { name: "Billy".to_string(), age: 15, height: 170, weight: 70, lifestate: LifeState::Alive, } } } fn main() { let character_1 = Character::default(); println!( "The character {:?} is {:?} years old.", character_1.name, character_1.age ); }
打印出The character "Billy" is 15 years old.
,简单多了!
现在我们来看建造者模式。我们会有很多Billy,所以我们会保留默认的。但是很多其他角色只会有一点不同。建造者模式让我们可以把小方法链接起来,每次改变一个值。这里是一个Character
的方法:
#![allow(unused)] fn main() { fn height(mut self, height: u32) -> Self { // 🚧 self.height = height; self } }
一定要注意,它取的是mut self
。我们之前看到过一次,它不是一个可变引用(&mut self
)。它占用了Self
的所有权,有了mut
,它将是可变的,即使它之前不是可变的。这是因为.height()
拥有完全的所有权,别人不能碰它,所以它是安全的,可变。它只是改变self.height
,然后返回Self
(就是Character
)。
所以我们有三个这样的构建方法。它们几乎是一样的:
#![allow(unused)] fn main() { fn height(mut self, height: u32) -> Self { // 🚧 self.height = height; self } fn weight(mut self, weight: u32) -> Self { self.weight = weight; self } fn name(mut self, name: &str) -> Self { self.name = name.to_string(); self } }
每一个都会改变一个变量,并回馈给Self
:这就是你在建造者模式中看到的。所以现在我们类似这样写来创建一个角色:let character_1 = Character::default().height(180).weight(60).name("Bobby");
。如果你正在构建一个库给别人使用,这可以让他们很容易用起来。对最终用户来说很容易,因为它几乎看起来像自然的英语。"给我一个默认角色,身高为180,体重为60,名字为Bobby." 到目前为止,我们的代码看起来是这样的:
#[derive(Debug)] struct Character { name: String, age: u8, height: u32, weight: u32, lifestate: LifeState, } #[derive(Debug)] enum LifeState { Alive, Dead, NeverAlive, Uncertain, } impl Character { fn new(name: String, age: u8, height: u32, weight: u32, alive: bool) -> Self { Self { name, age, height, weight, lifestate: if alive { LifeState::Alive } else { LifeState::Dead }, } } fn height(mut self, height: u32) -> Self { self.height = height; self } fn weight(mut self, weight: u32) -> Self { self.weight = weight; self } fn name(mut self, name: &str) -> Self { self.name = name.to_string(); self } } impl Default for Character { fn default() -> Self { Self { name: "Billy".to_string(), age: 15, height: 170, weight: 70, lifestate: LifeState::Alive, } } } fn main() { let character_1 = Character::default().height(180).weight(60).name("Bobby"); println!("{:?}", character_1); }
最后一个要添加的方法通常叫.build()
。这个方法是一种最终检查。当你给用户提供一个像.height()
这样的方法时,你可以确保他们只输入一个u32()
,但是如果他们输入5000的身高怎么办?这在你正在做的游戏中可能就不对了。我们最后将使用一个名为.build()
的方法,返回一个Result
。在它里面我们将检查用户输入是否正常,如果正常,我们将返回一个 Ok(Self)
。
不过首先我们要改变.new()
方法。我们不希望用户再自由创建任何一种角色。所以我们将把impl Default
的值移到.new()
。而现在.new()
不接受任何输入。
#![allow(unused)] fn main() { fn new() -> Self { // 🚧 Self { name: "Billy".to_string(), age: 15, height: 170, weight: 70, lifestate: LifeState::Alive, } } }
这意味着我们不再需要impl Default
了,因为.new()
有所有的默认值。所以我们可以删除impl Default
。
现在我们的代码是这样的。
#[derive(Debug)] struct Character { name: String, age: u8, height: u32, weight: u32, lifestate: LifeState, } #[derive(Debug)] enum LifeState { Alive, Dead, NeverAlive, Uncertain, } impl Character { fn new() -> Self { Self { name: "Billy".to_string(), age: 15, height: 170, weight: 70, lifestate: LifeState::Alive, } } fn height(mut self, height: u32) -> Self { self.height = height; self } fn weight(mut self, weight: u32) -> Self { self.weight = weight; self } fn name(mut self, name: &str) -> Self { self.name = name.to_string(); self } } fn main() { let character_1 = Character::new().height(180).weight(60).name("Bobby"); println!("{:?}", character_1); }
这样打印出来的结果是一样的:Character { name: "Bobby", age: 15, height: 180, weight: 60, lifestate: Alive }
。
我们几乎已经准备好写.build()
方法了,但是有一个问题:如何让用户使用它?现在用户可以写let x = Character::new().height(76767);
,然后得到一个Character
。有很多方法可以做到这一点,也许你能想出自己的方法。但是我们会在Character
中增加一个can_use: bool
的值。
#![allow(unused)] fn main() { #[derive(Debug)] // 🚧 struct Character { name: String, age: u8, height: u32, weight: u32, lifestate: LifeState, can_use: bool, // Set whether the user can use the character } \\ Cut other code fn new() -> Self { Self { name: "Billy".to_string(), age: 15, height: 170, weight: 70, lifestate: LifeState::Alive, can_use: true, // .new() always gives a good character, so it's true } } }
而对于其他的方法,比如.height()
,我们会将can_use
设置为false
。只有.build()
会再次设置为true
,所以现在用户要用.build()
做最后的检查。我们要确保height
不高于200,weight
不高于300。另外,在我们的游戏中,有一个不好的字叫smurf
,我们不希望任何角色使用它。
我们的.build()
方法是这样的:
#![allow(unused)] fn main() { fn build(mut self) -> Result<Character, String> { // 🚧 if self.height < 200 && self.weight < 300 && !self.name.to_lowercase().contains("smurf") { self.can_use = true; Ok(self) } else { Err("Could not create character. Characters must have: 1) Height below 200 2) Weight below 300 3) A name that is not Smurf (that is a bad word)" .to_string()) } } }
!self.name.to_lowercase().contains("smurf")
确保用户不会写出类似 "SMURF"或 "IamSmurf"的字样。它让整个 String
都变成小写(小字母),并检查 .contains()
而不是 ==
。而前面的!
表示 "不是"。
如果一切正常,我们就把can_use
设置为true
,然后把Ok
里面的字符给用户。
现在我们的代码已经完成了,我们将创建三个不工作的角色,和一个工作的角色。最后的代码是这样的。
#[derive(Debug)] struct Character { name: String, age: u8, height: u32, weight: u32, lifestate: LifeState, can_use: bool, // Here is the new value } #[derive(Debug)] enum LifeState { Alive, Dead, NeverAlive, Uncertain, } impl Character { fn new() -> Self { Self { name: "Billy".to_string(), age: 15, height: 170, weight: 70, lifestate: LifeState::Alive, can_use: true, // .new() makes a fine character, so it is true } } fn height(mut self, height: u32) -> Self { self.height = height; self.can_use = false; // Now the user can't use the character self } fn weight(mut self, weight: u32) -> Self { self.weight = weight; self.can_use = false; self } fn name(mut self, name: &str) -> Self { self.name = name.to_string(); self.can_use = false; self } fn build(mut self) -> Result<Character, String> { if self.height < 200 && self.weight < 300 && !self.name.to_lowercase().contains("smurf") { self.can_use = true; // Everything is okay, so set to true Ok(self) // and return the character } else { Err("Could not create character. Characters must have: 1) Height below 200 2) Weight below 300 3) A name that is not Smurf (that is a bad word)" .to_string()) } } } fn main() { let character_with_smurf = Character::new().name("Lol I am Smurf!!").build(); // This one contains "smurf" - not okay let character_too_tall = Character::new().height(400).build(); // Too tall - not okay let character_too_heavy = Character::new().weight(500).build(); // Too heavy - not okay let okay_character = Character::new() .name("Billybrobby") .height(180) .weight(100) .build(); // This character is okay. Name is fine, height and weight are fine // Now they are not Character, they are Result<Character, String>. So let's put them in a Vec so we can see them: let character_vec = vec![character_with_smurf, character_too_tall, character_too_heavy, okay_character]; for character in character_vec { // Now we will print the character if it's Ok, and print the error if it's Err match character { Ok(character_info) => println!("{:?}", character_info), Err(err_info) => println!("{}", err_info), } println!(); // Then add one more line } }
这将打印:
Could not create character. Characters must have:
1) Height below 200
2) Weight below 300
3) A name that is not Smurf (that is a bad word)
Could not create character. Characters must have:
1) Height below 200
2) Weight below 300
3) A name that is not Smurf (that is a bad word)
Could not create character. Characters must have:
1) Height below 200
2) Weight below 300
3) A name that is not Smurf (that is a bad word)
Character { name: "Billybrobby", age: 15, height: 180, weight: 100, lifestate: Alive, can_use: true }