You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JS函数式嵌套建造者模式重构:隐藏ItemBuilder实现链式调用

重构收据构建器:整合ItemBuilder实现无缝链式调用

现有两个JavaScript建造者函数:ReceiptBuilder用于构建包含名称、日期和商品列表的购物收据,ItemBuilder用于构建收据中的单个商品。原实现代码如下:

function Receipt(builder) {
  return {
    Name: builder.Name,
    Date: builder.Date,
    Items: builder.Items
  };
}

function ReceiptBuilder() {

  function WithName(name) {
    this.Name = name;
    return this;
  }

  function WithDate(dt) {
    this.Date = dt;
    return this;
  }

  function WithItem(item) {
    this.Items = this.Items || [];
    this.Items.push(item);
    return this;
  }

  function build() {
    return Receipt(this);
  }

  return {
    WithName,
    WithDate,
    WithItem,
    build
  }
}

function Item(builder) {
  return {
    Type: builder.Type,
    Price: builder.Price
  };
}

function ItemBuilder() {

  function WithType(type) {
    this.Type = type;
    return this;
  }

  function WithPrice(Price) {
    this.Price = Price;
    return this;
  }

  function build() {
    return Item(this);
  }

  return {
    WithPrice,
    WithType,
    build
  }
}

当前构建收据的方式需要显式调用ItemBuilder:

let receipt = ReceiptBuilder()
              .WithName('Shopping')
              .WithDate('01-01-01')
              .WithItem(ItemBuilder().
                        WithPrice(1.99).
                        WithType('eggs').build())
              .build()

需求是重构代码,将ItemBuilder融入ReceiptBuilder中,隐藏ItemBuilder的显式调用,实现如下链式构建方式:

ReceiptBuilder()
.WithName('Shopping')
.WithDate('01-01-01')
.WithItem()
.WithPrice(1.99)
.WithType('eggs')
.build()

重构后的代码实现

function Receipt(builder) {
  return {
    Name: builder.Name,
    Date: builder.Date,
    Items: builder.Items
  };
}

function Item(builder) {
  return {
    Type: builder.Type,
    Price: builder.Price
  };
}

function ReceiptBuilder() {
  // 存储当前正在构建的临时商品
  let currentItem = null;

  function WithName(name) {
    // 如果当前正在构建商品,先完成商品构建再处理收据属性
    if (currentItem) {
      finalizeCurrentItem();
    }
    this.Name = name;
    return this;
  }

  function WithDate(dt) {
    if (currentItem) {
      finalizeCurrentItem();
    }
    this.Date = dt;
    return this;
  }

  function WithItem() {
    // 如果之前有未完成的商品,先完成它
    if (currentItem) {
      finalizeCurrentItem();
    }
    // 创建新的临时商品对象
    currentItem = {};
    return this;
  }

  function WithPrice(price) {
    if (!currentItem) {
      throw new Error('必须先调用 WithItem() 来开始构建商品');
    }
    currentItem.Price = price;
    return this;
  }

  function WithType(type) {
    if (!currentItem) {
      throw new Error('必须先调用 WithItem() 来开始构建商品');
    }
    currentItem.Type = type;
    return this;
  }

  // 内部方法:完成当前商品构建并添加到商品列表
  function finalizeCurrentItem() {
    this.Items = this.Items || [];
    this.Items.push(Item(currentItem));
    currentItem = null;
  }

  function build() {
    // 构建前检查是否有未完成的商品
    if (currentItem) {
      finalizeCurrentItem();
    }
    return Receipt(this);
  }

  return {
    WithName,
    WithDate,
    WithItem,
    WithPrice,
    WithType,
    build
  };
}

关键改动说明

  • 整合商品构建逻辑:把ItemBuilder的WithPrice、WithType方法直接集成到ReceiptBuilder中,不再需要单独的ItemBuilder构造函数。
  • 临时商品状态管理:在ReceiptBuilder内部维护currentItem变量,用于存储当前正在构建的商品对象。调用WithItem()时初始化这个变量,后续的WithPrice、WithType直接修改它。
  • 自动完成商品构建:当切换到收据属性的方法(如WithName、WithDate)或者调用build()时,自动将当前未完成的商品添加到收据的商品列表中,避免遗漏。
  • 错误处理:如果在未调用WithItem()的情况下直接调用WithPrice或WithType,会抛出明确的错误提示,避免无效操作。

使用示例

const receipt = ReceiptBuilder()
  .WithName('Shopping')
  .WithDate('01-01-01')
  .WithItem()
  .WithPrice(1.99)
  .WithType('eggs')
  .WithItem() // 开始构建第二个商品
  .WithPrice(3.5)
  .WithType('milk')
  .build();

console.log(receipt);
// 输出:
// {
//   Name: 'Shopping',
//   Date: '01-01-01',
//   Items: [ { Type: 'eggs', Price: 1.99 }, { Type: 'milk', Price: 3.5 } ]
// }

内容的提问来源于stack exchange,提问作者stuwilmur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 10:35:21