JavaScript - 建造者模式

127 阅读3分钟

概念

将一个复杂对象的构建层与其表示层分离,同样的构建过程可以采用不同的并表示

工厂模式主要是为了创建对象实例或者类簇(抽象工厂),关心的是最终产出(创建)的是什么,而不关心创建的过程。而建造者模式关心的是创建这个对象的整个过程,甚至于创建对象的每一个细节。 以下以创建应聘者为例:应聘者有兴趣爱好,姓名和期望的职位等等

实现

// 创建一个人
function PersonBuilder(params) {
  this.skill = (params && params.skill) || "保密";
  this.hbody = (params && params.hbody) || "保密";
}
// 人的原型方法
PersonBuilder.prototype = {
  getSkill: function () {
    console.log(this.skill);
  },
  getHbody: function () {
    console.log(this.hbody);
  },
};

应聘者有姓名和工作,先实例化其姓名类和工作类:

//实例化姓名类
function Named(name) {
  var that = this;
  (function (name, that) {
    that.wholeName = name;
    if (name.indexOf(" ") > -1) {
      that.firstName = name.slice(0, name.indexOf(" "));
      that.secondName = name.slice(name.indexOf(" "));
    }
  })(name, that);
}

//实例化职位类
var Work = function (work) {
  var that = this;
  //构造器
  //构造函数中通过传入的职位特征来设置相应职位以及描述
  (function (work, that) {
    switch (work) {
      case "code":
        that.work = "工程师";
        that.workDescript = "每天沉醉于编程";
        break;
      case "UI":
      case "UE":
        that.work = "设计师";
        that.workDescript = "设计更似一种艺术";
        break;
      case "teach":
        that.work = "教师";
        that.workDescript = "分享也是一种快乐";
        break;
      default:
        that.work = work;
        that.workDescript = "对不起,我们还不清楚您所选择职位的相关描述";
    }
  })(work, that);
};
//更换期望的职位
Work.prototype.changeWork = function (work) {
  this.work = work;
};
//添加对职位的描述
Work.prototype.changeDescript = function (sentence) {
  this.workDescript = sentence;
};

这样就创建了抽象出来的3个类:应聘者类,姓名解析类和期望职位类。可以通过对这三个类的组合调用,写一个建造者类来创建出一个完整的应聘对象。

function Person(name, work) {
  var _person = new PersonBuilder();

  _person.name = new Named(name);
  _person.work = new Work(work);

  return _person;
}

var person = new Person("xiao ming", "code");
console.log(person);
person.work.changeDescript("更改描述!");
console.log(person.work.workDescript); //更改描述!

image.png

通过观察可以发现,建造者模式和工厂模式是有所不同的,建造者模式不仅可以得到创建的结果,而且参与了创建的具体过程,也干涉了创建的具体实现的细节。

ES6 实现

利用 class 语法实现 PersonBuilder,Named,Work 3 个类。

class PersonBuilder {
  constructor(params) {
    this.skill = (params && params.skill) || "保密";
    this.hbody = (params && params.hbody) || "保密";
  }

  getSkill() {
    console.log(this.skill);
  }

  getHbody() {
    console.log(this.hbody);
  }
}

class Named {
  constructor(params) {
    const that = this;
    (function (name, that) {
      that.wholeName = name;
      if (name.indexOf(" ") > -1) {
        that.firstName = name.slice(0, name.indexOf(" "));
        that.secondName = name.slice(name.indexOf(" "));
      }
    })(params.name, that);
  }
}

class Work {
  constructor(params) {
    const that = this;
    //构造器
    //构造函数中通过传入的职位特征来设置相应职位以及描述
    (function (work, that) {
      switch (work) {
        case "code":
          that.work = "工程师";
          that.workDescript = "每天沉醉于编程";
          break;
        case "UI":
        case "UE":
          that.work = "设计师";
          that.workDescript = "设计更似一种艺术";
          break;
        case "teach":
          that.work = "教师";
          that.workDescript = "分享也是一种快乐";
          break;
        default:
          that.work = work;
          that.workDescript = "对不起,我们还不清楚您所选择职位的相关描述";
      }
    })(params.work, that);
  }
  
  changeWork(work) {
    this.work = work;
  }

  changeDescript(sentence) {
    this.workDescript = sentence;
  }
}

去创造一个人的时候,需要同时继承上面的三个类,但是 JavaScript 语法是不允许同时继承多个类,例如:

class Person extends PersonBuilder,Named,Work {
    super( name,work )
}

这样的写法会抛出异常,所以我们需要来封装一个函数 mix,来实现多继承。

Mixins多继承

Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。

function mix(...mixins) {
  class Mix {
    // 如果不需要拷贝实例属性下面这段代码可以去掉;
    constructor(params) {
      for (let mixin of mixins) {
        copyProperties(this, new mixin(params)); // 拷贝实例属性
      }
    }
  }

  for (let mixin of mixins) {
    copyProperties(Mix, mixin); // 拷贝静态属性
    copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
  }

  return Mix;
}
// 深拷贝
function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if (key !== "constructor" && key !== "prototype" && key !== "name") {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}

使用方法:

class Person extends mix(PersonBuilder, Named, Work) {
  constructor(name, work) {
    super({ name, work });
  }
}