简介

建造者模式指将一个复杂对象的构造过程与表示分离,使同样的构建过程可以创建不同的表示。

它将变与不变相分离,即产品的整体组成不变,但是每一个部分是可以灵活变动的。

“表示”指的就是实际生成的实例对象,相同的构造过程可以创建不同的表示,这里我们需要先了解一下构造者模式中的3个角色。

角色:

  • 指挥者:调用具体建造者来创建复杂对象的各个部分,它只处理创建的过程
  • 建造者:提供各个部件的具体创建方法,在完成后提供产品的实例。
  • 产品:要创建的复杂对象

指挥者指挥建造者去建造产品,同一个指挥者指挥不同的建造者,可以产生不同的产品,而指挥者也是可以替换的,不同的指挥者指挥同一个建造者,也可以产生不同的产品。

但是最终这个产品从大类的角度去看,都是属于同一种。

举个实物例子:

C组装电脑,A客户要求用三星的内存条,B客户要求镁光内存条,最终组装出来的都是电脑,但是却又有不同。

客户的要求可以抽象成建造者的具体实现,C则是指挥者同时兼建造者。

而且建造者模式它可以不存在单独的指挥者(A它自己组装,它自己不就是指挥者和建造者了),具体在后面有一个例子。

面向接口思维

为了能让指挥者与建造者之间能够灵活替换而不会产生使用上的问题,那必然会需要面向接口编程,两方都依赖于抽象接口,不依赖具体实现。

代码实现

我们搞一个通过建造者模式生成dom元素的例子!

我们的产品:自定义的dom元素,它只是自定class,tag,id,text文本内容,通过调用show方法,他将会插入到body元素中。

产品的这些set方法都是提供的接口,用于给建造者建造不同的东西,这个就好比电脑的插槽,有内存插槽,有cpu插槽,建造者通过插槽传入不同的东西进来。

class MyDom {
  class: Array<string> = []; //class类名
  id: string = ""; //id
  tag: string = ""; //标签名
  text: string = ""; //文本

  constructor() {}

  setClass(classArr: string[]): void {
    this.class = classArr;
  }

  setId(id: string): void {
    this.id = id;
  }

  setTag(tag: string) {
    this.tag = tag;
  }

  setText(text: string) {
    this.text = text;
  }

  show() {
    //创建标签
    const dom = document.createElement(this.tag);
    //传入id
    dom.setAttribute("id", this.id);
    //设置class
    dom.classList.add(...this.class);
    //传入文本
    dom.innerText = this.text;

    //传入body
    document.body.appendChild(dom);
  }
}

产品有了,我们开始准备建造者:

先声明一个接口,接口有设置产品的方法,因为最终要提供一个获取产品的方法,这个方法没必要让子类去实现,所以直接写在父类了。

为了构建的时候省事,所以我要求每个方法最终返回this,已达到链式调用的效果。

abstract class Builders {
  protected dom: MyDom = new MyDom();

  public abstract setTag(): this;

  public abstract setText(): this;

  public abstract setClass(): this;

  public abstract setId(): this;

  public getDom(): MyDom {
    return this.dom;
  }
}

具体实现:

//创建一个div,并且设置id和class,已经内容
class CreateDiv extends Builders {
  setTag() {
    this.dom.setTag("div");
    return this;
  }

  setText() {
    this.dom.setText("我是一个通过建造者模式生成的div");
    return this;
  }

  setId() {
    this.dom.setId("box");
    return this;
  }

  setClass() {
    this.dom.setClass(["container", "font-weight-bold"]);
    return this;
  }
}

建造者有了,我们现在搞定指挥者:

先声明一个指挥者的抽象,用于给客户端去依赖。

abstract class Commander {
  abstract builder: Builders;

  public setBuilder(builder: Builders) {
    this.builder = builder;
  }

  public abstract create(): MyDom;
}

指挥者抽象存在一个builder的依赖,用于存放建造者对象,通过setBuilder方法。

create交给具体子类去实现。

class MyDivCommander extends Commander {
  builder!: Builders;

  create() {
    //构建过程
    const builder = this.builder.setTag().setId().setClass().setText();

    return builder.getDom();
  }
}

客户端去实际使用:

function create() {
  const createDiv = new CreateDiv();
  const myDivCommander:Commander = new MyDivCommander();
  //传入建造者
  myDivCommander.setBuilder(createDiv);

  //调用指挥者获取dom
  const dom = myDivCommander.create();

  //使用dom
  dom.show();
}

在java中可能会用一个类去实现,而不是函数,那么它对于抽象Commander的要求可能会更明确一些。

运行create我们可以创建出一个自定义的div元素。

不需要单独的指挥者

上述代码中,其实指挥者的存在仿佛有点多余,有时候我们没必要一定要去实现一个指挥者,根据业务需求可能直接通过客户去调用建造者,客户自己去控制构建流程并使用。

function create() {
  const createDiv = new CreateDiv();
  let dom: MyDom;

  //自己构建
  dom = createDiv.setTag().setId().setClass().setText().getDom();

  //使用dom
  dom.show();
}

create();
建造者(Builder)模式在应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色。

建造者模式的应用场景

当需要创建的产品具备复杂创建过程时,可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,使创建产品的灵活性大大增加。

建造者模式主要适用于以下场景:

  • 相同的方法,不同的执行顺序,产生不同的结果。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不同。
  • 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
  • 初始化一个对象比较复杂,参数多,而且很多参数都具有默认值。

总结

我们可以灵活的切换指挥者和建造者,他们都是基于抽象,而具体的实现会由子类去完成,能保证调用上的一致性。

以上述代码来说,如果我想要创建一个不同的元素,我只需要生成不同的建造者子类就可以了,使用同一个指挥者生成不同的dom元素,使用不同的指挥者,也能生成不同的dom元素。

建造者模式用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。

优点

  1. 产品的建造与表示分离,实现了解耦。使用建造者不需要知道具体产品实现细节。
  2. 将复杂的产品创建步骤分解在不同的方法中,使得创建过程更加清晰。
  3. 具体的建造者之间是独立的,这有利于系统的扩展,增加新的具体建造者无需修改原有的类库代码,符合开闭原则。

缺点

  1. 建造者模式所创建的产品-般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  2. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
分类: 设计模式 标签: 设计模式建造者模式指挥者建造者产品客户

评论

暂无评论数据

暂无评论数据

目录