Builder Pattern
Table of Contents
Background
Often, we need to create complex objects with many parameters, some mandatory, some optional.
The Builder pattern helps us create them step by step and expose a nice friendly API to the client.
Common use cases
HTTP clients often use the builder pattern to create requests.
For example reqest is a popular Rust HTTP client that uses the builder pattern.
let echo_json: serde_json::Value = reqwest::Client::new()
.post("https://jsonplaceholder.typicode.com/posts")
.json( & serde_json::json!({
"title": "Reqwest.rs",
"body": "https://docs.rs/reqwest",
"userId": 1
}))
.send()
.await?
.json()
.await?;
In this example we can see the following steps:
- Create a new client.
- Create a new
POSTrequest. - Set the request body to a JSON object.
- Send the request.
awaitthe response.- Parse the response as
JSON.
This is a very clear and concise way to describe the flow and any reader will understand with ease.
How to build your own
- Define your
structandbuilder.
#[derive(Debug)]
struct House {
walls: i32,
doors: i32,
windows: i32,
}
#[derive(Debug)]
struct HouseBuilder {
walls: i32,
doors: i32,
windows: i32,
status: u8,
}
- Implement the
constructorto thebuilder.
pub fn new() -> HouseBuilder {
HouseBuilder {
walls: 0,
doors: 0,
windows: 0,
status: 0b0000_0000,
}
}
- Implement the
settermethods.
pub fn walls(&mut self, walls: i32) -> &mut Self {
self.walls = walls;
self.status = self.status | 0b0000_0001;
self
}
pub fn doors(&mut self, doors: i32) -> &mut Self {
self.doors = doors;
self.status = self.status | 0b0000_0010;
self
}
pub fn windows(&mut self, windows: i32) -> &mut Self {
self.windows = windows;
self.status = self.status | 0b0000_0100;
self
}
- Finally implement the
buildmethod.
pub fn build(&self) -> Result<House, HouseBuilderError> {
if self.status != 0b0000_0111 {
return Err(HouseBuilderError);
}
Ok(House {
walls: self.walls,
doors: self.doors,
windows: self.windows,
})
}
To the keen reader, you may have noticed the status field.
This is a bitmask that we use to keep track of the fields that have been set.
Which allows us to use Result and let the client know if there is any issue.
fn main() {
let house1 = HouseBuilder::new().doors(2).windows(4).walls(1).build();
assert!(house1.is_ok());
if let Ok(house1) = house1 {
println!("house1 {:#?}", house1);
assert_eq!(2, house1.doors);
assert_eq!(4, house1.windows);
assert_eq!(1, house1.walls);
}
let house2 = HouseBuilder::new().windows(4).walls(3).doors(1).build();
assert!(house2.is_ok());
if let Ok(house2) = house2 {
println!("house2 {:#?}", house2);
assert_eq!(1, house2.doors);
assert_eq!(4, house2.windows);
assert_eq!(3, house2.walls);
}
let house3 = HouseBuilder::new().windows(4).build();
assert!(house3.is_err());
if let Err(e) = house3 {
println!("house3 {:#?}", e);
}
}