Rust 精簡構造器

在這篇短文中,我們將介紹構造器模式的表親 -- 精簡構造器。

不像傳統的構造器,需要一個單獨的構造器對象,精簡構造器重用對象本身來提供構造器的功能。

我們來看一個例子:

精簡構造器

pub struct Shape {
  position: Vec3,
  geometry: Geometry,
  material: Option<Material>,
}
impl Shape {
  pub fn new(geometry: Geometry) -> Shape {
    Shape {
      position: Vec3::default(),
      geometry,
      material: None,
    }
  }
  pub fn with_position(mut self, position: Vec3) -> Shape {
    self.position = position;
    self
  }
  pub fn with_material(mut self, material: Material) -> Shape {
    self.material = Some(material);
    self
  }
}
// Call site
let shape = Shape::new(Geometry::Sphere::with_radius(1))
  .with_position(Vec3(0, 9, 2))
  .with_material(Material::SolidColor(Color::Red));

完整構造器

相比之下,完整構造器在定義上明顯更加冗長,且需要兩次額外的調用,如下:

pub struct Shape {
  position: Vec3,
  geometry: Geometry,
  material: Option<Material>,
}
pub struct ShapeBuilder {
  position: Option<Vec3>,
  geometry: Option<Geometry>,
  texture: Option<Texture>,
}
impl Shape {
  pub fn builder() -> ShapeBuilder { ... }
}
impl ShapeBuilder {
  pub fn position(&mut self, position: Vec3) -> &mut Self { ... }
  pub fn geometry(&mut self, geometry: Geometry) -> &mut Self { ... }
  pub fn material(&mut self, material: Material) -> &mut Self { ... }
  pub fn build(&self) -> Shape { ... }
}
// Call site
let shape = Shape::builder()
  .position(Vec3(9, 2))
  .geometry(Geometry::Sphere::with_radius(1))
  .material(Material::SolidColor(Color::Red))
  .build();

精簡構造器最主要的好處就是它可以增量的、零成本的方式構建對象。因此,在代碼以不確定的方向快速發展的情況下,它特別有用。在工作中,找到了一個實際的例子,如下:

impl PeerManagerActor {
  pub fn new(
    store: Store,
    config: NetworkConfig,
    client_addr: Recipient<NetworkClientMessages>,
    view_client_addr: Recipient<NetworkViewClientMessages>,
    routing_table_addr: Addr<RoutingTableActor>,
  ) -> anyhow::Result<Self> {

這裏有一個 new 方法,及一大堆各種依賴的參數。我們需要做的是,還要增加另外的依賴,爲了能在測試中被重寫。首先嚐試的是,在 new 方法中加入新的參數,如下:

pub fn new(
    store: Store,
    config: NetworkConfig,
    client_addr: Recipient<NetworkClientMessages>,
    view_client_addr: Recipient<NetworkViewClientMessages>,
    routing_table_addr: Addr<RoutingTableActor>,
+   ping_counter: Box<dyn PingCounter>,
  ) -> anyhow::Result<Self> {

然而這種方式,我們需要修改 7 處調用 new 方法的地方。替換成精簡構造器模式,我們就只需要修改一處,也就是我們關心的 counter 部分。

命名注意事項

如果構造器的方法恰巧被使用了,with_foo 是最好的命名方式。對於布爾類型,有時需要同時寫 2 個構造器方法,如下:

pub fn fancy(mut self) -> Self {
  self.with_fancy(true)
}
pub fn with_fancy(mut self, yes: bool) -> Self {
  self.fancy = yes;
  self
}

本文翻譯自:

https://matklad.github.io/2022/05/29/builder-lite.html

coding 到燈火闌珊 專注於技術分享,包括 Rust、Golang、分佈式架構、雲原生等。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/g1Yg4XFBDBtIPnYf1eC97Q