Builder 设计模式的目标就是将一个简单对象的构建与示意拆散,使同样的构建过程能够有不同的示意。
例如有一个 XML 文件解析程序,一种需要可能将一个 xml 文件转换成 json 格局的数据,而另一个需要须要将 xml 文件转换成 csv 文本模式。一种可能的实现形式就是实现一个独立的 xml 文本解析器,解析器从前往后解析文本,遇到不同的标签格局时告诉特定的构建器构建后果。解析实现时由构建器返回最终的构建后果。
上面咱们以创立一个简略的模板引擎为例理解 builder 模式:
对于模板引擎大家肯定很相熟,比方 Mustache、pug、ejs,understore 的 template 办法,这些模板的应用通常都是传入一段模板自字符串,返回一个编译好的函数,通过一个上下文对象作为参数调用这个函数就能够失去一段 html 文本字符串。
比方 pug 的应用形式如下(例子来自于 pug 中文文档):const pug = require('pug');
const compiledFunction = pug.compileFile('template.pug');
console.log(compiledFunction({name: '李莉'}));
// "<p> 李莉的 Pug 代码!</p>"
// 渲染另外一组数据
console.log(compiledFunction({name: '张伟'}));
// "<p> 张伟的 Pug 代码!</p>"
接下来咱们来写一个本人的模板解析工具:
假如当初有一个这样的模板字符串:
const tpl = `
<p>{{name}}</p>
{{if isVip}}
<p>Welcome!</p>
{{/endif}}
`;
在下面的模板中 {{}} 示意一个插值,{{if isVip}} …{{/endif}}示意一个条件编译语句,只有在 isVip 为 true 时,之间的 html 内容才会被渲染。
首先定义一个解析器:
class Parser {static parse(tpl, builder) {const htmlReg = /^s*([^{}]+)/;
const interpReg = /^s*{{s*(w+?)s*}}/;
const ifReg = /^s*{{ifs+(w+?)s*}}(.*?){{/endif}}/;
// 去除换行和多余的空格
tpl = tpl.replace(/n+/g, '').replace(/s{2,}/g,' ');
while(tpl.length) {
// 一般标签
if (htmlReg.test(tpl)) {const match = tpl.match(htmlReg);
const label = match[0];
tpl = tpl.substr(label.length);
// html += (html === ''?'' : '+') + JSON.stringify(label.trim());
builder.html(label.trim());
} else if(ifReg.test(tpl)) { // 条件语句
const match = tpl.match(ifReg);
const label = match[0];
tpl = tpl.substr(label.length);
builder.condition(...match.slice(1, 3).map(s => s.trim()));
} else if(interpReg.test(tpl)) { // 插值语句
const match = tpl.match(interpReg);
const label = match[0];
const name = match[1];
tpl = tpl.substr(label.length);
// html += '+' + '$$option.' + name;
builder.interpolation(name);
} else {break;}
}
}
}
解析器的有一个 parse 办法,该办法承受两个参数,一个待解析的模板字符串以及一个构建器,构建器须要提供至多三个办法 html,interpolattion,condition 这三个办法别离用于解决解析器解析进去的一般 html、插值表达式、以及条件语句。依照雷同的形式能够扩大出其余更多的管制语句。
上面时 builder 类的实现:
class Builder {constructor(ctxName = '$$option') {
this._result = '';
this._ctxName = ctxName;
}
result() {return this._result;}
html(html) {this._result += (this._result === ''?'' : '+') + JSON.stringify(html);
}
condition(name, content) {
this._result += this._result === ''?'' : '+';
this._result += `
(${this._ctxName}.${name}?${JSON.stringify(content)}:${JSON.stringify('')})
`;
}
interpolation(name) {this._result += (this._result === ''?'' : '+') + `${this._ctxName}.` + name;
}
}
Builder 类的 result 办法返回最终失去的代码字符串。
联合下面的两个类,实现的编译函数如下:
function compile(tpl) {const builder = new Builder();
Parser.parse(tpl, builder);
return new Function('$$option', `return ${builder.result()}`);
}
compile 函数首先构建一个 builder 实例,通过该实例调用 parse 办法,最终返回一个函数用于执行从 builder 失去的代码字符串。
接下来测试一下这个程序:
const template = compile(tpl);
console.log(template({name: '小明', isVip: true}));
执行后果如下:
<p> 小明 </p><p>Welcome!</p>