这条款项描述了构造器模式:对于复杂的数据类型提供对应的构造器类型buildertype,使得用户可以方便地创造该数据数据类型的实例。Rust要求开发者在创建一个新的struct实例的时候,必须填入struct的所有字段。这样可以保证结构体中永远不会存在未初始化的值,从而保证了代码的安
这条款项描述了构造器模式:对于复杂的数据类型提供对应的构造器类型 builder type
,使得用户可以方便地创造该数据数据类型的实例。
Rust 要求开发者在创建一个新的 struct
实例的时候,必须填入 struct
的所有字段。这样可以保证结构体中永远不会存在未初始化的值,从而保证了代码的安全,然而这会比理想的情况下产生更多的冗余的代码片段。
例如,任何可选的字段都必须显式地使用 None
来标记为缺失:
/// Phone number in E164 format.
#[derive(Debug, Clone)]
pub struct PhoneNumberE164(pub String);
#[derive(Debug, Default)]
pub struct Details {
pub given_name: String,
pub preferred_name: Option<String>,
pub middle_name: Option<String>,
pub family_name: String,
pub mobile_phone: Option<PhoneNumberE164>,
}
// ...
let dizzy = Details {
given_name: "Dizzy".to_owned(),
preferred_name: None,
middle_name: None,
family_name: "Mixer".to_owned(),
mobile_phone: None,
};
这样的样板式代码也很脆弱,因为将来要向 struct
中添加一个新字段的时候需要更改所有创建这个结构体的地方。
通过使用和实现 [Default] trait 可以显著地减少这种样板代码,如[第 10 条]中所述:
let dizzy = Details {
given_name: "Dizzy".to_owned(),
family_name: "Mixer".to_owned(),
..Default::default()
};
使用 Default
还有助于减少结构体新增字段时候导致的修改,前提是新的字段本身的类型也实现了 Default
。
还有一个更普遍的问题:仅当所有的字段类型都实现了 Default
trait 的时候,结构体才能使用自动派生的 Default
实现。如果有任何一个字段不满足,那么 derive
就会失败了:
#[derive(Debug, Default)]
pub struct Details {
pub given_name: String,
pub preferred_name: Option<String>,
pub middle_name: Option<String>,
pub family_name: String,
pub mobile_phone: Option<PhoneNumberE164>,
pub date_of_birth: time::Date,
pub last_seen: Option<time::OffsetDateTime>,
}
error[E0277]: the trait bound `Date: Default` is not satisfied
--> src/main.rs:48:9
|
41 | #[derive(Debug, Default)]
| ------- in this derive macro expansion
...
48 | pub date_of_birth: time::Date,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Default` is not
| implemented for `Date`
|
= note: this error originates in the derive macro `Default`
由于孤儿规则的存在,代码没办法为 chrono::Utc
实现 Default
;但就算可以,也无济于事 —— 给出生日期赋一个值默认值几乎总是一个错误的选择。
缺少 Default
意味着所有字段都必须手动填写:
let bob = Details {
given_name: "Robert".to_owned(),
preferred_name: Some("Bob".to_owned()),
middle_name: Some("the".to_owned()),
family_name: "Builder".to_owned(),
mobile_phone: None,
date_of_birth: time::Date::from_calendar_date(
1998,
time::Month::November,
28,
)
.unwrap(),
last_seen: None,
};
如果你为复杂的数据结构实现了构造器模式,那么就可以提高这里的效率和体验。
构造器模式最简单的一种实现方式就是用一个额外的 struct
来保存构造原始复杂数据类型所需的数据。简单起见,这里的实例会直接保存一个该类型的实例:
pub struct DetailsBuilder(Details);
impl DetailsBuilder {
/// Start building a new [`Details`] object.
/// 开始构造一个新的 [`Details`] 对象
pub fn new(
given_name: &str,
family_name: &str,
date_of_birth: time::Date,
) -> Self {
DetailsBuilder(Details {
given_name: given_name.to_owned(),
preferred_name: None,
middle_name: None,
family_name: family_name.to_owned(),
mobile_phone: None,
date_of_birth,
last_seen: None,
})
}
}
随后,我们可以给构造器类型增添辅助函数来填充新的字段。每一个这种函数都会消费 self
同时产生一个新的 Self
,以允许对不同的构造方法进行链式调用。
这些辅助函数会比简单的 setter
函数有用多了:
/// Update the `last_seen` field to the current date/time.
/// 把 `last_seen` 字段更新成当前日期/时间
pub fn just_seen(mut self) -> Self {
self.0.last_seen = Some(time::OffsetDateTime::now_utc());
self
}
构造器被调用的最后一个函数会消费它自身并输出所构造的对象:
/// Consume the builder object and return a fully built [`Details`]
/// object.
/// 消费构造器对象并返回最后创建的 [`Details`] 对象
pub fn build(self) -> Details {
self.0
}
总而言之,这让构造器的使用者拥有了更符合工程学的体验:
let also_bob = DetailsBuilder::new(
"Robert",
"Builder",
time::Date::from_calendar_date(1998, time::Mon...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!