本文通过综合使用 builder pattern, enums和traits来构造一个程序,输出不同语言版本的hello world
// 第一个版本的代码,我们希望输出不同语言的hello world
// greet函数接收参数,根据参数来决定输出哪种语言的hello world
// 可以发现该代码有个缺陷, greet的参数是&str类型,如果调用greet时拼写错误,则不容易发现问题, 因此这种字符串对比的场景更推荐使用enum
fn greet(language: &str) {
if language == "english" {
println!("Hello World");
}
}
fn main() {
greet("english");
greet("English"); // 不容易察觉的问题
}
使用enum来改进字符串对比
//使用enum改进
// greet函数中换成了match
enum Language {
English,
German,
Chinese,
}
fn greet(language: Language) {
match language {
Language::English => println!("Hello World"),
Language::German => println!("Hallo Welt"),
Language::Chinese => println!("你好,世界"),
}
}
fn main() {
greet(Language::English);
greet(Language::Chinese);
}
Hello World
你好,世界
还可以对greet函数改进,使其更符合rust习惯写法。
另外我们希望有一种默认语言,比如不指定语种时,默认为英语的“hello world”, 但是rust语言不支持keyword argument,想实现类似的效果,可以使用builder pattern
enum Language {
English,
German,
Chinese,
}
struct Greeter {
language: Language,
}
impl Greeter {
fn new() -> Self {
Self {
language: Language::English,
}
}
fn with_language(mut self, language: Language) -> Self {
self.language = language;
self // 返回Greeter类型,为了在main中调用时使用chaining写法
}
fn greet(&self) {
let greeting = match self.language {
Language::English => "Hello World",
Language::German => "Hallo Welt",
Language::Chinese => "你好,世界",
};
println!("{}", greeting);
}
}
fn main() {
let greeter = Greeter::new();
greeter.greet();
let greeter_zh = Greeter::new().with_language(Language::Chinese);
greeter_zh.greet();
}
Hello World
你好,世界
以上代码可以运行,但先实例化一个Greeter实例,然后再调用 greet()方法,感觉上有点重复,如果可以直接打印Greeter实例就能达到效果呢?
实际上,rust确实提供了这种可能,通过实现Display trait, 官网对Display trait的描述如下,还附有一个例子,接下来我们也对代码进行改造,使其实现Display trait:
use std::fmt;
enum Language {
English,
German,
Chinese,
}
struct Greeter {
language: Language,
}
impl fmt::Display for Greeter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let greeting = match self.language {
Language::English => "Hello World",
Language::German => "Hallo Welt",
Language::Chinese => "你好,世界",
};
write!(f, "{}", greeting)
}
}
impl Greeter {
fn new() -> Self {
Self {
language: Language::English,
}
}
fn with_language(mut self, language: Language) -> Self {
self.language = language;
self // 返回Greeter类型,为了在main中调用时使用chaining写法
}
}
fn main() {
let greeter = Greeter::new();
println!("{}", greeter);
let greeter_zh = Greeter::new().with_language(Language::Chinese);
println!("{}", greeter_zh);
let greeter_de = Greeter::new().with_language(Language::German);
assert_eq!(format!("{}", greeter_de), "Hallo Welt");
}
Builder Pattern
Rust has the following problems:
- no default values for function arguments
- no function overloading
- no keyword arguments
- therefore no support for optional arguments
the workaround: abusing the builder pattern to simulate optional arguments
useful crate: derive builder
//From Python to Rust中的例子
// 我们希望在实例化User的时候, id、email是必传参数,而first_name和last_name可选
#[derive(Debug)]
struct User {
id: i32,
email: String,
first_name: Option<String>,
last_name: Option<String>,
}
struct UserBuilder {
id: i32,
email: String,
first_name: Option<String>,
last_name: Option<String>,
}
// rust社区约定俗成,builder的名字为xxx+Buider
impl UserBuilder {
// 类似于python中 id 和 email 是必传的参数, first_name和 last_name是可选的
fn new(id: impl Into<i32>, email: impl Into<String>) -> Self {
Self {
id: id.into(),
email: email.into(),
first_name: None,
last_name: None,
}
}
fn first_name(mut self, first_name: impl Into<String>) -> Self {
// 返回Self是为了chaining写法
self.first_name = Some(first_name.into());
self
}
fn last_name(mut self, last_name: impl Into<String>) -> Self {
self.last_name = Some(last_name.into());
self
}
fn build(self) -> User {
// consume Builder and create the desired struct instance
let Self {
id,
email,
first_name,
last_name,
} = self; // destruct, so that we can use the property names without colon notation
User {
id,
email,
first_name,
last_name,
}
}
}
impl User {
fn builder(id: impl Into<i32>, email: impl Into<String>) -> UserBuilder {
UserBuilder::new(id, email)
}
fn complete(&self) -> bool {
self.last_name.is_some()
&& self.id > 0
&& !self.email.is_empty()
&& self.first_name.is_some()
}
}
fn main() {
// 使用Builder Pattern模拟keyword arguments
let bob = User::builder(13, "bob@example.com")
.first_name("Bob")
.build();
println!("Complete? {}", bob.complete());
println!("bob_the_builder = {:#?}\n", bob);
}
参考