前言
本文主要讲一下怎么使用 rust 来实现构造器模式,可以通过链式调用创建自定义的 struct。 首先会介绍一下常见创建 struct 实例的几种方式,然后以创建一个 RequestInfo 的结构来讲解如何实现构造器模式。
1.创建 struct 实例的常用方式
1.1 实现 new
#[derive(Debug)]
struct Task {
title: String,
done: bool,
desc: Option<String>,
}
// 使用 impl Into<String>, 参数可以传入 String、&String 和 &str
impl Task {
pub fn new(title: impl Into<String>) -> Task {
Task {
title: title.into(),
done: false,
desc: None,
}
}
}
impl Default for Task {
fn default() -> Self {
Self {
title: "unknown task".to_string(),
done: false,
desc: None,
}
}
}
let task = Task::new("Test Task");
println!("{:?}", task) // Task { title: "Test Task", done: false, desc: None }
let task = Task::default();
println!("{:?}", task) // Task { title: "unknown task", done: false, desc: None }
// 实现了 default 后,可以结合 unwrap 使用
let task: Option<Task> = None;
let task = task.unwrap_or_default();
println!("{:?}", task); // Task { title: "unknown task", done: false, desc: None }
// 实现了 default 后,可以结合 .. 使用
let task = Task {
done: true,
..Default::default()
};
println!("{:?}", task); // Task { title: "unknown task", done: true, desc: None }
2. request builder 的实现,&self(非消费构建模式),
这个实现方法中,build 函数的传参是 &self, 所以我们 build 的时候里面的值需要 clone,因为我们不能改变它,也取不了它的值,这样可以多次调用函数以保证每次调用的结果一致。
#[derive(Debug)]
struct Request {
pub method: String, // enum
pub url: String,
pub version: String,
pub header: Vec<(String, String)>, // name, value
pub body: Option<String>,
}
#[derive(Default, Debug)]
struct RequestBuilder {
pub method: Option<String>,
pub url: Option<String>,
pub version: Option<String>,
pub header: Vec<(String, String)>,
pub body: Option<String>,
}
impl RequestBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn method(&mut self, method: impl Into<String>) -> &mut Self {
self.method = Some(method.into());
self
}
pub fn url(&mut self, url: impl Into<String>) -> &mut Self {
self.url = Some(url.into());
self
}
pub fn version(&mut self, version: impl Into<String>) -> &mut Self {
self.version = Some(version.into());
self
}
pub fn body(&mut self, body: impl Into<String>) -> &mut Self {
self.body = Some(body.into());
self
}
pub fn header(&mut self, name: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.header.push((name.into(), value.into()));
self
}
// 注意,此时build传入的参数是 &self
pub fn build(&self) -> Result<Request, &'static str> {
// 没有 url 就报错
let Some(url) = self.url.as_ref() else {
return Err("url is required");
};
// method 和 version 提供一个默认值
let method = self
.method
.as_ref()
.cloned()
.unwrap_or_else(|| "GET".to_string());
let version = self
.version
.as_ref()
.cloned()
.unwrap_or_else(|| "HTTP/1.1".to_string());
Ok(Request {
method,
url: url.to_string(),
version,
header: self.header.clone(),
body: self.body.clone(),
})
}
}
fn main() {
let req = RequestBuilder::new()
.url("http://localhost:8080")
.method("POST")
.version("HTTP/1.1")
.header("Content-Type", "application/json")
.build();
println!("{:?}", req); // Ok(Request { method: "POST", url: "http://localhost:8080", version: "HTTP/1.1", header: [("Content-Type", "application/json")], body: None })
let req = RequestBuilder::new()
.url("http://localhost:7777")
.method("GET")
.version("HTTP/2.1")
.build();
println!("{:?}", req); // Ok(Request { method: "GET", url: "http://localhost:7777", version: "HTTP/2.1", header: [], body: None })
// 把公用的提出来
let mut req_builder = RequestBuilder::new();
req_builder
.url("http://localhost:8080")
.method("POST")
.version("HTTP/1.1");
req_builder.header("Content-Type", "application/json");
req_builder.header("token", "test/token");
let req = req_builder.build();
println!("{:?}", req); //Ok(Request { method: "POST", url: "http://localhost:8080", version: "HTTP/1.1", header: [("Content-Type", "application/json"), ("token", "test/token")], body: None })
}
3. request builder 的实现,self (消费构建模式)
消费构建是把 struct 的所有权川传进 build 函数里面,需要修改一些地方。
impl RequestBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn method(mut self, method: impl Into<String>) -> Self {
self.method = Some(method.into());
self
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
pub fn version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
pub fn body(mut self, body: impl Into<String>) -> Self {
self.body = Some(body.into());
self
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.header.push((name.into(), value.into()));
self
}
// 此时传参变为了 self, 且其他函数都是接受 self 返回 Self
pub fn build(self) -> Result<Request, &'static str> {
// 没有 url 就报错
let Some(url) = self.url else {
return Err("url is required");
};
// method 和 version 提供一个默认值
let method = self.method.unwrap_or_else(|| "GET".to_string());
let version = self.version.unwrap_or_else(|| "HTTP/1.1".to_string());
Ok(Request {
method,
url,
version,
header: self.header,
body: self.body,
})
}
}
fn main() {
// 此时每次返回的都是 struct 的所有权
let req_builder = RequestBuilder::new()
.url("http://localhost:8080")
.method("POST")
.version("HTTP/1.1");
let req = req_builder
.header("Content-Type", "application/json")
.header("token", "test/token")
.build();
println!("{:?}", req);
// 如果我们想创建另一个 Requset, 会报错,因为 build 之后, req_builder 已经给了 req 了,再用就会报错了
let req = req_builder.header("test3", "test3"); // error[E0382]: use of moved value: `req_builder`
// 这种消耗型的构建模型就需要给全局 struct 添加 clone 的 trait
#[derive(Default, Debug, Clone)]
struct RequestBuilder {
pub method: Option<String>,
pub url: Option<String>,
pub version: Option<String>,
pub header: Vec<(String, String)>,
pub body: Option<String>,
}
let req_builder = RequestBuilder::new()
.url("http://localhost:8080")
.method("POST")
.version("HTTP/1.1");
let req_builder = req_builder
.header("Content-Type", "application/json")
.header("token", "test/token");
// 然后在 build 的时候先 clone 一次,创建新的就不会报错了
let req = req_builder.clone().build();
}
总结
本文主要讲述了在 rust 中如何使用构造器模式去自定义地创建自己需要的数据结构,并且探讨了在构造实例时,构造函数的参数接受 struct 引用还是接收 struct 本身的所有权两种方法对于实现链式调用的影响。