Rust 构造器模式的实现

227 阅读4分钟

前言

本文主要讲一下怎么使用 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 本身的所有权两种方法对于实现链式调用的影响。