JS 中的函数式嵌套构建器模式

问题描述 投票:0回答:1

考虑两个构建器函数:一个用于构建购物收据,其中包含名称、日期和商品列表;另一个用于构建购物收据。第二个用于在收据中构建每个购物项目。简单的实现如下:

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
  }
}

然后我们可以按如下方式建立收据:

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

是否可以重构以允许第二个构建器以某种方式“折叠”到第一个构建器中,以实现以下(或类似的)构建调用? IE。以某种方式“隐藏” ItemBuilder 会很好。

ReceiptBuilder()
.WithName('Shopping')
.WithDate('01-01-01')
.WithItem()
.WithPrice(1.99)
.WithType('eggs')
.build()
javascript functional-programming builder
1个回答
2
投票

只需进行最少的更改,

WithItem()
方法就可以启动 ItemBuilder 并从中链接。需要有一些表明该项目已完成的标志,该标志将链接回 ReceiptBuilder。这是一个名为
endBuildingItem()
:

的方法
function WithItem() {
  this.Items = this.Items || [];
  
  const receiptBuilder = this;
  
  return Object.assign(ItemBuilder(), { 
    endBuildingItem() {
      receiptBuilder.Items.push(this.build());
      return receiptBuilder;
    } 
  });
}

const receipt1 = ReceiptBuilder()
  .WithName('Shopping')
  .WithDate('01-01-01')
  .WithItem()
    .WithPrice(1.99)
    .WithType('eggs')
    .endBuildingItem()
  .build();
  
console.log(receipt1);

const receipt2 = ReceiptBuilder()
  .WithName('Shopping')
  .WithDate('01-01-01')
  .WithItem()
    .WithPrice(2.99)
    .WithType('milk')
    .endBuildingItem()
  .WithItem()
    .WithPrice(3.99)
    .WithType('bread')
    .endBuildingItem()
  .build();
  
console.log(receipt2);

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() {
    this.Items = this.Items || [];
    
    const receiptBuilder = this;
    
    return Object.assign(ItemBuilder(), { 
      endBuildingItem() {
        receiptBuilder.Items.push(this.build());
        return receiptBuilder;
      } 
    });
  }

  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
  }
}
.as-console-wrapper { max-height: 100% !important; }

另一种实现是将

endBuildingItem()
方法放入 ItemBuilder 中,并期望将 ReceiptBuilder 的实例传递给其中:

const receipt1 = ReceiptBuilder()
  .WithName('Shopping')
  .WithDate('01-01-01')
  .WithItem()
    .WithPrice(1.99)
    .WithType('eggs')
    .endBuildingItem()
  .build();
  
console.log(receipt1);

const receipt2 = ReceiptBuilder()
  .WithName('Shopping')
  .WithDate('01-01-01')
  .WithItem()
    .WithPrice(2.99)
    .WithType('milk')
    .endBuildingItem()
  .WithItem()
    .WithPrice(3.99)
    .WithType('bread')
    .endBuildingItem()
  .build();
  
console.log(receipt2);

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() {
    this.Items = this.Items || [];
    
    return ItemBuilder(this);
  }

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

  return {
    WithName,
    WithDate,
    WithItem,
    build
  }
}

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

function ItemBuilder(receiptBuilder) {

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

  function WithPrice(Price) {
    this.Price = Price;
    return this;
  }
  
  function build() {
    return Item(this);
  }
  
  function endBuildingItem() {
    receiptBuilder.Items.push(this.build());
    return receiptBuilder;
  }

  return {
    WithPrice,
    WithType,
    build,
    endBuildingItem,
  }
}
.as-console-wrapper { max-height: 100% !important; }

© www.soinside.com 2019 - 2024. All rights reserved.