生成器模式

生成器 Builder

生成器模式也叫“建造者模式”是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。


为什么要使用?

生成器模式的对象职责:

  • 分步骤创建复杂对象。 分阶段、分步骤的方法更适合多次运算结果类创建场景。在实际开发中,并非所有参数都能一次性准备好的,需要通过其他运算后才能得出。这时我们有 3 种方法可以实现:

    1. 构造函数: 如果参数很多,并且有许多不必要生成的参数,那么我们就需要根据各种情况,编写各种构造函数,或者在全参构造函数中为那些不必要的参数传值。代码臃肿、多余。

    2. Setter 方法: 如果使用 Setter 方法,就需要用无参构造先创建对象,再一个个 Setter 属性,如果属性过多,而且要生成的对象多,也是很麻烦、冗余。

    3. 生成器模式: 生成器模式可以帮助你分步骤的创建一个复杂对象,虽然生成器模式本身会占用一定的资源。但如果要创建很多对象或创建的对象很复杂,这点资源也可以忽略不计。例如创建一个学生对象:

      Student student = new Student.Builder().name("张三").age(18).sex("男").build();
      
  • 使用相同的创建代码生成不同类型和形式的对象。 不需要关系特定类型的建造者的具体算法实现。例如很多框架都会使用生成器模式去创建对象,我们在使用这些框架去创建某些对象时,不需要关注其内部逻辑,只需要着重关注它能给我们带来什么功能,这样能提高开发效率。


模式结构

  • 生成器 (Builder) 接口声明在所有类型生成器中通用的产品构造步骤。
  • 具体生成器 (Concrete Builders) 提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品。
  • 产品 (Products) 是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口。
  • 主管 (Director) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。
  • 客户端 (Client) 必须将某个生成器对象与主管类关联。 一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用生成器对象完成后续所有的构造任务。 但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。 在这种情况下, 你在使用主管类生产产品时每次都可以使用不同的生成器。

生成器模式的类图:

在这里插入图片描述

生成器模式的顺序图:

在这里插入图片描述


模式实现

该示例通过使用生成器模式分别创建复杂对象 HTML 页面和 Markdown 文档。

示例程序的类图

在这里插入图片描述

代码实现
文档生成器 Builder
/** 生成器接口,统一文档编写接口 */
public interface Builder {
    /**
     * 创建标题
     * @param title 标题内容
     */
    void makeTitle(String title);

    /**
     * 创建无序列表
     * @param items 列表项
     */
    void makeItems(String[] items);

    /**
     * 创建内容
     * @param content 内容
     */
    void makeContent(String content);
}
HTML生成器 HTMLBuilder
/** HTML文档实现类 */
public class HTMLBuilder implements Builder {
    private String html = "";
    /**
     * 创建标题
     * @param title 标题内容
     */
    @Override
    public void makeTitle(String title) {
        html += "<html><head><title>" + title + "</title></head></html>" + "\n";
    }

    /**
     * 创建无序列表
     * @param items 列表项
     */
    @Override
    public void makeItems(String[] items) {
        html += "<ul>" + "\n";
        for (String item : items) {
            html += "<li>" + item + "</li>" + "\n";
        }
        html += "</ul>" + "\n";
    }

    /**
     * 创建内容
     * @param content 内容
     */
    @Override
    public void makeContent(String content) {
        html += "<p>" + content + "</p>" + "\n";
    }

    /**
     * 获取HTML文档
     * @return HTML文档字符串
     */
    public String getResult() {
        return html;
    }
}
Markdown文档生成器 MarkdownBuilder
/** markdown文档实现类 */
public class MarkdownBuilder implements Builder {
    private String markdown = "";
    /**
     * 创建标题
     * @param title 标题内容
     */
    @Override
    public void makeTitle(String title) {
        markdown += "## " + title + "\n";
    }

    /**
     * 创建无序列表
     * @param items 列表项
     */
    @Override
    public void makeItems(String[] items) {
        for (String item : items) {
            markdown += "* " + item + "\n";
        }
    }

    /**
     * 创建内容
     * @param content 内容
     */
    @Override
    public void makeContent(String content) {
        markdown += content + "\n";
    }

    /**
     * 获取markdown文档
     * @return markdown文档字符串
     */
    public String getResult() {
        return markdown;
    }
}
编写文档主管 Director
/** 主管类,决定生成的文档类型 */
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("这是一段内容!");
    }
}
代码测试
/** 测试生成器模式 */
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());
    }
}
输出结果
用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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值