生成器 Builder 生成器模式 也叫“建造者模式”是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。
为什么要使用? 生成器模式的对象职责:
分步骤创建复杂对象。 分阶段、分步骤的方法更适合多次运算结果类创建场景。在实际开发中,并非所有参数都能一次性准备好的,需要通过其他运算后才能得出。这时我们有 3 种方法可以实现:
构造函数: 如果参数很多,并且有许多不必要生成的参数,那么我们就需要根据各种情况,编写各种构造函数,或者在全参构造函数中为那些不必要的参数传值。代码臃肿、多余。
Setter 方法: 如果使用 Setter
方法,就需要用无参构造先创建对象,再一个个 Setter
属性,如果属性过多,而且要生成的对象多,也是很麻烦、冗余。
生成器模式: 生成器模式可以帮助你分步骤的创建一个复杂对象,虽然生成器模式本身会占用一定的资源。但如果要创建很多对象或创建的对象很复杂,这点资源也可以忽略不计。例如创建一个学生对象:
1 Student student = new Student.Builder().name("张三" ).age(18 ).sex("男" ).build();
使用相同的创建代码生成不同类型和形式的对象。 不需要关系特定类型的建造者的具体算法实现。例如很多框架都会使用生成器模式去创建对象,我们在使用这些框架去创建某些对象时,不需要关注其内部逻辑,只需要着重关注它能给我们带来什么功能,这样能提高开发效率。
模式结构
生成器 (Builder) 接口声明在所有类型生成器中通用的产品构造步骤。
具体生成器 (Concrete Builders) 提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品。
产品 (Products) 是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口。
主管 (Director) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。
客户端 (Client) 必须将某个生成器对象与主管类关联。 一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用生成器对象完成后续所有的构造任务。 但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。 在这种情况下, 你在使用主管类生产产品时每次都可以使用不同的生成器。
生成器模式的类图:
生成器模式的顺序图:
模式实现 该示例通过使用生成器模式分别创建复杂对象 HTML 页面和 Markdown 文档。
示例程序的类图
代码实现 文档生成器 Builder 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public interface Builder { void makeTitle (String title) ; void makeItems (String[] items) ; void makeContent (String content) ; }
HTML生成器 HTMLBuilder 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class HTMLBuilder implements Builder { private String html = "" ; @Override public void makeTitle (String title) { html += "<html><head><title>" + title + "</title></head></html>" + "\n" ; } @Override public void makeItems (String[] items) { html += "<ul>" + "\n" ; for (String item : items) { html += "<li>" + item + "</li>" + "\n" ; } html += "</ul>" + "\n" ; } @Override public void makeContent (String content) { html += "<p>" + content + "</p>" + "\n" ; } public String getResult () { return html; } }
Markdown文档生成器 MarkdownBuilder 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class MarkdownBuilder implements Builder { private String markdown = "" ; @Override public void makeTitle (String title) { markdown += "## " + title + "\n" ; } @Override public void makeItems (String[] items) { for (String item : items) { markdown += "* " + item + "\n" ; } } @Override public void makeContent (String content) { markdown += content + "\n" ; } public String getResult () { return markdown; } }
编写文档主管 Director 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Director { private Builder builder; public Director (Builder builder) { this .builder = builder; } public void construct () { builder.makeTitle("这是一个标题" ); String[] items = {"无序列表1" , "无序列表2" , "无序列表3" }; builder.makeItems(items); builder.makeContent("这是一段内容!" ); } }
代码测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Test { public static void main (String[] args) { System.out.println("用HTMLBuilder生成文档" ); HTMLBuilder htmlBuilder = new HTMLBuilder(); Director director1 = new Director(htmlBuilder); director1.construct(); System.out.println(htmlBuilder.getResult()); System.out.println("----------------- 分割线 -----------------\n" ); System.out.println("用MarkdownBuilder生成文档" ); MarkdownBuilder markdownBuilder = new MarkdownBuilder(); Director director2 = new Director(markdownBuilder); director2.construct(); System.out.println(markdownBuilder.getResult()); } }
输出结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 用HTMLBuilder生成文档 <html > <head > <title > 这是一个标题</title > </head > </html > <ul > <li > 无序列表1</li > <li > 无序列表2</li > <li > 无序列表3</li > </ul > <p > 这是一段内容!</p > ----------------- 分割线 ----------------- 用MarkdownBuilder生成文档 ## 这是一个标题 * 无序列表1* 无序列表2* 无序列表3这是一段内容!
我们也可以直接跳过 Director
,直接使用 Builder
,或者在 Director
里编写多个方法,注入不同 Builder
。
常用场景和解决方案
需要生成的对象包含多个成员属性,使用生成器模式可以避免“重叠构造函数”和“繁琐 Setter”的出现。
需要生成的对象的属性相互依赖,需要指定其生成顺序。
对象的创建过程独立于创建该对象的类。
需要隔离复杂对象的创建和使用,并使用相同的创建过程可以创建不同的产品。
模式的优缺点
优点
缺点
你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。
生成不同形式的产品时, 你可以复用相同的制造代码。
单一职责原则 。 你可以将复杂构造代码从产品的业务逻辑中分离出来。
使用生成器模式的优势
分离创建与使用,使用方不需要知道类的内部实现逻辑细节,通过统一接口调用,可以组合出不同类型的对象。
满足开闭原则,每一个生成器都相对独立,可以很容易替换或新增,提高代码的可拓展性。
自由地组合对象的创建过程,使用者可以使用少量代码灵活创建满足自己需求的对象。
使用生成器模式的劣势
使用范围有限。
容易引起超大的类。
增加代码行数。
拓展知识
生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂 会马上返回产品, 生成器 则允许你在获取产品前执行一些额外构造步骤。
你可以结合使用生成器和桥接模式:主管 类负责抽象工作,各种不同的生成器 负责实现 工作。
抽象工厂、 生成器和原型都可以用单例模式来实现。
设计时要谨记“只有不知道子类才能替换”。
🔙 设计模式
📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!
🔗参考文献:
🌐 设计模式 –refactoringguru
▶️ bilibili-趣学设计模式;黄靖锋. –拉勾教育
📖 图解设计模式 /(日)结城浩著;杨文轩译. –北京:人民邮电出版社,2017.1