精读《设计模式 - Builder 生成器》

<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px; color: black; padding: 0 10px; line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; word-break: break-word; word-wrap: break-word; text-align: left; font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;"><h1 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 24px;"><span class="prefix" style="display: none;"></span><span class="content">Builder(生成器)</span><span class="suffix"></span></h1>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">Builder(生成器)属于建立型模式,针对的是单个复杂对象的建立。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">意图:将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。</p>
<h2 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">举例子</span><span class="suffix"></span></h2>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">若是看不懂上面的意图介绍,没有关系,设计模式须要在平常工做里用起来,结合例子能够加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。</p>
<h3 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;"><span class="prefix" style="display: none;"></span><span class="content">搭乐高积木</span><span class="suffix" style="display: none;"></span></h3>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">乐高积木是很典型的随机拼装场景,你有不少乐高积木,要搭一个小房子都太复杂了,可能不得不看着说明书一步步操做,这就像建立一个复杂的对象,要传入很是多的参数,并且顺序还不能错。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">若是不考虑拼装乐高过程当中的乐趣,你只是想快速获得一个标准的房子,怎么样才能够最快最省事?</p>
<h3 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;"><span class="prefix" style="display: none;"></span><span class="content">工厂流水线</span><span class="suffix" style="display: none;"></span></h3>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">制做一个罐头要经历许多步骤,而其中一些步骤好比制做罐头是通用的,能够用这个罐头装不少东西,好比红枣罐头、黄桃罐头,那工厂流水线是怎么作到灵活可拓展的呢?</p>
<h3 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;"><span class="prefix" style="display: none;"></span><span class="content">建立数据库链接池</span><span class="suffix" style="display: none;"></span></h3>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">创建一个数据库链接池,咱们须要传入数据库的地址、用户名与密码、还有要建立多少大小的链接池,缓存的位置等等。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">考虑到数据库必须正确链接后才有效,建立时必须校验传入的数据库地址与密码的正确性,甚至存储方式与数据库类型还有关系,这是一个简单的 new 实例化能够解决的吗?</p>
<h2 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">意图解释</span><span class="suffix"></span></h2>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在乐高积木的例子中,咱们为了获得一个房子其实不须要关心每个积木应该如何摆放,咱们只要交给组装工厂(一我的或者一个程序)产出标准房子就好了,这其中参数多是 .setHouseType().build() 设置房屋类型,而不须要 new House(block1, block2, ... block999) 传递这些不必的参数。其中组装工厂就是生成器。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在工厂流水线的例子中,流水线就是生成器,一个流水线能够不经过不一样组合生成不一样做用的工厂,黄桃罐头的流水线能够理解为 new Builder().组装罐头().放入黄桃().build(),红枣罐头的流水线能够理解为 new Builder().组装罐头().放入红枣().build(),咱们能够复用生成器最基础的函数 组装罐头() 将其用于建立不一样的产品中,复用了组装基础能力。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在建立数据库例子中,咱们能够先设置一些必要的参数再建立,好比 new Builder().setUrl().setPassword().setType().build(),这样在最终执行 build 函数的时候,能够对参数中存在关联的进行校验,而获得的对象也没法再被修改,这样比直接暴露数据库链接池对象,再一个值一个值 Set 多了以下好处:</p>
<ol data-tool="mdnice编辑器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: decimal;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">对象没法被修改,保护了程序稳定性,减小了维护复杂度。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">能够对参数关联进行一次性校验。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在建立对象以前不会存在中间态,即建立了对象实例,但缺乏部分参数,这可能致使对象没法正确 work。</section></li></ol>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">意图:将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">咱们再理解一次意图,所谓构建与表示分离,就是指一个对象 Persion 并非简单的 new Persion() 就能够实例化出来的,若是能够,那就是构建与表示一体。所谓构建与表示分离,就是指 Persion 只能描述,而不能经过 new Persion() 实例化,将实例化工做经过 Builder 实现,这样一样一个构建过程能够建立不一样的 Persion 实例。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在乐高积木的例子中,经过乐高建立的房子并非 new House() 出来,而是将构建与表示分离了,工厂流水线中咱们建立一个黄桃罐头,不是经过 new 黄桃罐头(),而是经过流水线不一样拼装方式来完成,在数据库例子中,咱们没有经过 new DB() 的方式建立数据库,而是经过 Builder 来建立,这都体现了构建与表示的分离。</p>
<h2 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">结构图</span><span class="suffix"></span></h2>

<ul data-tool="mdnice编辑器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: disc;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">Director 指导器,用来指导构建过程。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">Builder 生成器接口,用来提供一系列构建对象的方法,以及最终的 build 生成对象函数,这个函数里能够作一些参数校验。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">ConcreteBuilderBuilder 的具体实现。</section></li></ul>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">实际上,Builder 模式抽象层次可高可低,咱们上面三个例子都没有用到指导器与生成器接口,这是由于在代码不太复杂的状况下,可使用简化模型。</p>
<h2 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">代码例子</span><span class="suffix"></span></h2>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">下面例子使用 javascript 编写。</p>
<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px;"><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">class</span> Director {
<span/> create(concreteBuilder: ConcreteBuilder) {
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 建立了一些零件</span>
<span/> concreteBuilder.buildA();
<span/> concreteBuilder.buildB();
<span/>
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 校验参数已经生成实例</span>
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">return</span> concreteBuilder.build();
<span/> }
<span/>}
<span/>
<span/><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">class</span> HouseBuilder {
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">public</span> buildA() {
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 建立房屋</span>
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// this.xxx = xxx</span>
<span/> }
<span/>
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">public</span> buildB() {
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 刷油漆</span>
<span/> }
<span/>
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">public</span> build() {
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 最终建立实例</span>
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">return</span> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">new</span> House(<span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">/ ..一堆参数 this.xxx.. /</span>);
<span/> }
<span/>}
<span/>
<span/><span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 接下来是正式使用</span>
<span/><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">const</span> director = <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">new</span> Director();
<span/><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">const</span> builder = HouseBuilder();
<span/><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">const</span> house = director.create(builder);
<span/>
</pre>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">上面的例子是完整版本的 Builder 模式,抽象了指导器 Director 与生成器 Builder,只要二者都严格按照接口实现,咱们能够:</p>
<ol data-tool="mdnice编辑器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: decimal;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">替换任意 Director,使建立的过程作任意修改。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">替换任意 Builder,使建立的实现作任意修改。</section></li></ol>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">作了任意的改动,均可以获得不一样的房子实现,这就是建立与表示分离的好处,咱们能够经过一样的构建过程建立不一样的表示。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">这个 director.create():</p>
<ul data-tool="mdnice编辑器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: disc;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在搭乐高积木的例子,表示用乐高搭建房屋的过程。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在工程流水线的例子,表示罐头的组装构成。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在建立数据库链接池的例子,表示数据库链接池的建立过程。</section></li></ul>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">而 Builder 以及其函数 buildA buildB 等方法表示具体制造方法,好比:</p>
<ul data-tool="mdnice编辑器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: disc;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在搭乐高积木的例子,表示如何盖房子,如何刷油漆。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在工程流水线的例子,表示如何作一个罐头,如何添加黄桃。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在建立数据库链接池的例子,表示如何设置数据库地址,如何设置用户名密码等。</section></li></ul>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">对于数据库的例子中,咱们不只能够保证建立对象的便捷性,由于不须要传入过多参数,也保证了对象的正确校验,同时生成的实例也是不可变的。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">更重要的是,若是使用完整模式,咱们能够替换 Director 来修改建立数据库的方式,替换 Builder 来修改具体方法,好比 .setUserName 这个函数不作具体实现,而是统计性能,build() 函数建立的不是一个数据库链接实例,而是一个测试实例。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">再好比前端同一个方法在 JS 和 Node 环境下运行效果不同,咱们能够实现 BrowserBuildNodeBuild,实现相同的接口,这样能够共享相同的建立过程,建立不一样环境能够运行的实例。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">能够看到,使用 Builder 模式能够保证建立对象的便捷与稳定性,还留了足够的拓展空间改变对象的建立过程与建立方法,具备极强的拓展性。</p>
<h2 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">弊端</span><span class="suffix"></span></h2>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">任何设计模式都有其适用场景,反过来也说明了在某些场景下不适用。</p>
<ul data-tool="mdnice编辑器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: disc;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">实例化对象很是繁琐,重复定义了许多对象成员变量的 set 方法,并且也不如 new 看的直观,也就是场景足够简单时,不须要任何地方都用 Builder 实例化对象。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">一个对象只有一种表示时,不必作如此地步的抽象。</section></li></ul>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">上面的例子都是相对复杂的,假设咱们的搭房子的例子中,咱们不是用乐高积木搭建,而是用两块半成品模板拼起来就获得一个房子,那就没有必要使用 Builder 模式,直接 new House() 便可。</p>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">再者,若是咱们只须要生产各类罐头,而不须要生产汽车,那么就不必过分抽象 Builder,把建立汽车的方法也囊括进去,最后,若是咱们的对象只有一种表示时,没有必要抽象 Builder,也就是流水线若是只生产黄桃罐头,就不必把各个生产环节变成可拆卸的,由于也没有从新组合的须要。</p>
<h2 data-tool="mdnice编辑器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">总结</span><span class="suffix"></span></h2>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">Builder 模式对于建立一个复杂对象特别有用,能够看下图加深理解:</p>

<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">最后总结一下什么时候适合用 Builder 模式:只有当建立过程容许被构造对象有不一样表示,或者对象复杂到对象描述与建立对象过程值得分离时,才使用 Builder 设计模式。</p>
<blockquote class="multiquote-1" data-tool="mdnice编辑器" style="border: none; display: block; font-size: 0.9em; overflow: auto; overflow-scrolling: touch; border-left: 3px solid rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.05); color: #6a737d; padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 10px; margin-bottom: 20px; margin-top: 20px;">
<p style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0px; color: black; line-height: 26px;">讨论地址是:精读《设计模式 - Builder 生成器》· Issue #273 · dt-fe/weekly</p>
</blockquote>
<p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">若是你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。</p>
<blockquote class="multiquote-1" data-tool="mdnice编辑器" style="border: none; display: block; font-size: 0.9em; overflow: auto; overflow-scrolling: touch; border-left: 3px solid rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.05); color: #6a737d; padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 10px; margin-bottom: 20px; margin-top: 20px;">
<p style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0px; color: black; line-height: 26px;">关注 前端精读微信公众号</p>
</blockquote>

<blockquote class="multiquote-1" data-tool="mdnice编辑器" style="border: none; display: block; font-size: 0.9em; overflow: auto; overflow-scrolling: touch; border-left: 3px solid rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.05); color: #6a737d; padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 10px; margin-bottom: 20px; margin-top: 20px;">
<p style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0px; color: black; line-height: 26px;">版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)</p>
</blockquote>javascript

<p id="nice-suffix-juejin-container" class="nice-suffix-juejin-container" data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black; margin-top: 20px !important;">本文使用 mdnice 排版</p></section>Builder(生成器)

Builder(生成器)属于建立型模式,针对的是单个复杂对象的建立。前端

意图:将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。java

举例子

若是看不懂上面的意图介绍,没有关系,设计模式须要在平常工做里用起来,结合例子能够加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。git

搭乐高积木

乐高积木是很典型的随机拼装场景,你有不少乐高积木,要搭一个小房子都太复杂了,可能不得不看着说明书一步步操做,这就像建立一个复杂的对象,要传入很是多的参数,并且顺序还不能错。github

若是不考虑拼装乐高过程当中的乐趣,你只是想快速获得一个标准的房子,怎么样才能够最快最省事?web

工厂流水线

制做一个罐头要经历许多步骤,而其中一些步骤好比制做罐头是通用的,能够用这个罐头装不少东西,好比红枣罐头、黄桃罐头,那工厂流水线是怎么作到灵活可拓展的呢?数据库

建立数据库链接池

创建一个数据库链接池,咱们须要传入数据库的地址、用户名与密码、还有要建立多少大小的链接池,缓存的位置等等。设计模式

考虑到数据库必须正确链接后才有效,建立时必须校验传入的数据库地址与密码的正确性,甚至存储方式与数据库类型还有关系,这是一个简单的 new 实例化能够解决的吗?缓存

意图解释

在乐高积木的例子中,咱们为了获得一个房子其实不须要关心每个积木应该如何摆放,咱们只要交给组装工厂(一我的或者一个程序)产出标准房子就好了,这其中参数多是 .setHouseType().build() 设置房屋类型,而不须要 new House(block1, block2, ... block999) 传递这些不必的参数。其中组装工厂就是生成器微信

在工厂流水线的例子中,流水线就是生成器,一个流水线能够不经过不一样组合生成不一样做用的工厂,黄桃罐头的流水线能够理解为 new Builder().组装罐头().放入黄桃().build(),红枣罐头的流水线能够理解为 new Builder().组装罐头().放入红枣().build(),咱们能够复用生成器最基础的函数 组装罐头() 将其用于建立不一样的产品中,复用了组装基础能力。

在建立数据库例子中,咱们能够先设置一些必要的参数再建立,好比 new Builder().setUrl().setPassword().setType().build(),这样在最终执行 build 函数的时候,能够对参数中存在关联的进行校验,而获得的对象也没法再被修改,这样比直接暴露数据库链接池对象,再一个值一个值 Set 多了以下好处:

  1. 对象没法被修改,保护了程序稳定性,减小了维护复杂度。
  2. 能够对参数关联进行一次性校验。
  3. 在建立对象以前不会存在中间态,即建立了对象实例,但缺乏部分参数,这可能致使对象没法正确 work。

意图:将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。

咱们再理解一次意图,所谓构建与表示分离,就是指一个对象 Persion 并非简单的 new Persion() 就能够实例化出来的,若是能够,那就是构建与表示一体。所谓构建与表示分离,就是指 Persion 只能描述,而不能经过 new Persion() 实例化,将实例化工做经过 Builder 实现,这样一样一个构建过程能够建立不一样的 Persion 实例。

在乐高积木的例子中,经过乐高建立的房子并非 new House() 出来,而是将构建与表示分离了,工厂流水线中咱们建立一个黄桃罐头,不是经过 new 黄桃罐头(),而是经过流水线不一样拼装方式来完成,在数据库例子中,咱们没有经过 new DB() 的方式建立数据库,而是经过 Builder 来建立,这都体现了构建与表示的分离。

结构图

  • Director 指导器,用来指导构建过程。
  • Builder 生成器接口,用来提供一系列构建对象的方法,以及最终的 build 生成对象函数,这个函数里能够作一些参数校验。
  • ConcreteBuilderBuilder 的具体实现。

实际上,Builder 模式抽象层次可高可低,咱们上面三个例子都没有用到指导器与生成器接口,这是由于在代码不太复杂的状况下,可使用简化模型。

代码例子

下面例子使用 javascript 编写。

class Director {
 create(concreteBuilder: ConcreteBuilder) {
 // 建立了一些零件
 concreteBuilder.buildA();
 concreteBuilder.buildB();
 // 校验参数已经生成实例
 return concreteBuilder.build();
 }
}
 class HouseBuilder {
 public buildA() {
 // 建立房屋
 // this.xxx = xxx
 }
 public buildB() {
 // 刷油漆
 }
 public build() {
 // 最终建立实例
 return new House(/* ..一堆参数 this.xxx.. */);
 }
}
 // 接下来是正式使用
const director = new Director();
const builder = HouseBuilder();
const house = director.create(builder);

上面的例子是完整版本的 Builder 模式,抽象了指导器 Director 与生成器 Builder,只要二者都严格按照接口实现,咱们能够:

  1. 替换任意 Director,使建立的过程作任意修改。
  2. 替换任意 Builder,使建立的实现作任意修改。

作了任意的改动,均可以获得不一样的房子实现,这就是建立与表示分离的好处,咱们能够经过一样的构建过程建立不一样的表示。

这个 director.create()

  • 在搭乐高积木的例子,表示用乐高搭建房屋的过程。
  • 在工程流水线的例子,表示罐头的组装构成。
  • 在建立数据库链接池的例子,表示数据库链接池的建立过程。

Builder 以及其函数 buildA buildB 等方法表示具体制造方法,好比:

  • 在搭乐高积木的例子,表示如何盖房子,如何刷油漆。
  • 在工程流水线的例子,表示如何作一个罐头,如何添加黄桃。
  • 在建立数据库链接池的例子,表示如何设置数据库地址,如何设置用户名密码等。

对于数据库的例子中,咱们不只能够保证建立对象的便捷性,由于不须要传入过多参数,也保证了对象的正确校验,同时生成的实例也是不可变的。

更重要的是,若是使用完整模式,咱们能够替换 Director 来修改建立数据库的方式,替换 Builder 来修改具体方法,好比 .setUserName 这个函数不作具体实现,而是统计性能,build() 函数建立的不是一个数据库链接实例,而是一个测试实例。

再好比前端同一个方法在 JS 和 Node 环境下运行效果不同,咱们能够实现 BrowserBuildNodeBuild,实现相同的接口,这样能够共享相同的建立过程,建立不一样环境能够运行的实例。

能够看到,使用 Builder 模式能够保证建立对象的便捷与稳定性,还留了足够的拓展空间改变对象的建立过程与建立方法,具备极强的拓展性。

弊端

任何设计模式都有其适用场景,反过来也说明了在某些场景下不适用。

  • 实例化对象很是繁琐,重复定义了许多对象成员变量的 set 方法,并且也不如 new 看的直观,也就是场景足够简单时,不须要任何地方都用 Builder 实例化对象。
  • 一个对象只有一种表示时,不必作如此地步的抽象。

上面的例子都是相对复杂的,假设咱们的搭房子的例子中,咱们不是用乐高积木搭建,而是用两块半成品模板拼起来就获得一个房子,那就没有必要使用 Builder 模式,直接 new House() 便可。

再者,若是咱们只须要生产各类罐头,而不须要生产汽车,那么就不必过分抽象 Builder,把建立汽车的方法也囊括进去,最后,若是咱们的对象只有一种表示时,没有必要抽象 Builder,也就是流水线若是只生产黄桃罐头,就不必把各个生产环节变成可拆卸的,由于也没有从新组合的须要。

总结

Builder 模式对于建立一个复杂对象特别有用,能够看下图加深理解:

最后总结一下什么时候适合用 Builder 模式:只有当建立过程容许被构造对象有不一样表示,或者对象复杂到对象描述与建立对象过程值得分离时,才使用 Builder 设计模式。

讨论地址是: 精读《设计模式 - Builder 生成器》· Issue #273 · dt-fe/weekly

若是你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

版权声明:自由转载-非商用-非衍生-保持署名( 创意共享 3.0 许可证
相关文章
相关标签/搜索