1.什么是建造者模式?
建造者模式(Builder)是将一个复杂对象的构建层与其表示层相互分离,同样的构建过程采用不同的表示。
建造者模式的特点是分步构造一个复杂的对象,可以用不同组合或顺序建造出不同意义的对象,通常使用者并不需要知道建造的细节,通常使用链式调用来进行建造过程,最后调用build方法来生成最终对象。
同样作为创建型的设计模式,需要注意和工程模式的区别,工程虽然也是创建对象,单怎样创建无所谓,工程模式关注的是创建的结果;而建造者模式不仅得到了结果,同时也参与了创建的具体过程,适合用来创建一个复杂的复合对象。
2.ES6中的建造者模式
下面我们来假设一个出版社的书籍后台录入系统的业务场景,书籍有四个必填信息,分别是书名,作者,价格,分类;我们希望创建一个书籍对象返回给后端。下面我们来一步一步深入使用ES6的语法结合建造者模式创建对象。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
</head>
<body>
<script>
// 书籍建造者类
class BookBuilder{
constructor(){
this.name = '';
this.author = '';
this.price = 0;
this.category = '';
}
withName(){
this.name = name;
return this;
}
withAuthor(author){
this.author = author;
return this;
}
withPrice(price){
this.price = price;
return this;
}
withCategory(category){
this.category = category;
return this;
}
build(){
return{
name:this.name,
author:this.author,
price:this.price,
category:this.category
}
}
}
// 调用建造者类
const book = new BookBuilder()
.withName("高效能人士的七个习惯")
.withAuthor('史蒂芬·柯维')
.withPrice(51)
.withCategory('励志')
.build();
console.log(book);
</script>
</body>
</html>
上面就通过BookBuilder这个创建者类的写法和调用方法,但是仅仅是一个4个属性的对象,我们使用了如此多的代码去创建,这远比直接再constructor传递参数创建对象要复杂得多。这是由于在创建的过程中,我们有太多的withxxxx方法。我们其实可以自动创建这类withxxxx方法以简化代码。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
</head>
<body>
<script>
//书籍建造者类
class BookBuilder {
constructor() {
this.name = '';
this.author = '';
this.price = 0;
this.category = '';
Object.keys(this).forEach(key => {
const withName = `with${key.substring(0, 1).toUpperCase()}${key.substring(1)}`;
this[withName] = value => {
this[key] = value;
return this;
}
console.log('key=',key)
console.log('withName=',withName)
console.log('this[withName]=',this[withName])
})
}
//调用建造者
build() {
const keysNoWithers = Object.keys(this).filter(key => typeof this[key] !== 'function');
console.log('keysNoWithers=',keysNoWithers);
return keysNoWithers.reduce((returnValue, key) => { // reduce()方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值
return {
...returnValue,
[key]: this[key]
}
}, {})
}
}
const book = new BookBuilder().withName("高效能人士的七个习惯").withAuthor('史蒂芬·柯维').withPrice(51).withCategory('励志').build();
console.log('book=',book);
</script>
</body>
</html>
上面的BookBuilder这个类和第一个例子的效果一样,但是长度确实减少不少,在有更多属性的属性的时候,减少的代码量会更为明显。我们将所有的建造方法withxxxx在constructor调用时自动被创建你,这里我们使用了一些ES6的新语法:Object.keys获取对象属性数组,…的合并对象的语法。
虽然该写法在阅读起来会比第一个方法难以理解,但是这样写法的真正作用在于,当我们需要许多的建造者类时,我们可以将上面自动创建withxxxx和build的代码提取为一个父类。在创建其它建造者类时继承该父类,这使得在创建多个建造者类时变得十分容易。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
</head>
<body>
<script>
//父类
class BaseBuilder {
init() {
Object.keys(this).forEach(key => {
const withName = `with${key.substring(0, 1).toUpperCase()}${key.substring(1)}`;
this[withName] = value => {
this[key] = value;
return this;
}
})
}
build() {
const keysNoWithers = Object.keys(this).filter(key => typeof this[key] !== 'function');
return keysNoWithers.reduce((returnValue, key) => {
return { ...returnValue,
[key]: this[key]
}
}, {})
}
}
//子类1: 书籍建造者类
class BookBuilder extends BaseBuilder {
constructor() {
super();
this.name = '';
this.author = '';
this.price = 0;
this.category = '';
super.init();
}
}
//子类2: 印刷厂建造者类
class printHouseBuilder extends BaseBuilder {
constructor() {
super();
this.name = '';
this.location = '';
this.quality = '';
super.init();
}
}
//调用书籍建造者类
const book = new BookBuilder().withName("高效能人士的七个习惯").withAuthor('史蒂芬·柯维').withPrice(51).withCategory('励志').build();
//调用印刷厂建造类
const printHouse = new printHouseBuilder().withName('新华印刷厂').withLocation('北京海淀区').withQuality('A').build();
console.log('book=', book);
console.log('printHouse=', printHouse);
</script>
</body>
</html>
可以看出,建造者模式的使用有且只适合创建极为复杂的对象。在前端的实际业务中,在没有这类极为复杂的对象的创建时,还是应该直接使用对象字面或工厂模式等方式创建对象。