设计模式学习(十一):Builder建造者模式

简介: 设计模式学习(十一):Builder建造者模式

一、什么是Builder模式



大都市中林立着许多高楼大厦,这些高楼大厦都是具有建筑结构的大型建筑。通常,建造和构建这种具有建筑结构的大型物体在英文中称为Build。


在建造大楼时,需要先打牢地基,搭建框架,然后自下而上地一层一层盖起来。通常,在建造这种具有复杂结构的物体时,很难一气呵成。我们需要首先建造组成这个物体的各个部分,然后分阶段将它们组装起来。


用一句话来概括:Builder模式用于组装具有复杂结构的实例。

4e920d83058da525fd42325bc2850a44.png


二、Builder模式示例代码



这是一段使用Builder模式编写“文档”的程序。这里编写出的文档含有一个标题、几个字符串、条目项目。


Builder类中定义了决定文档结构的方法,然后Director类使用该方法编写一个具体的文档。


Builder是抽象类,它并没有进行任何实际的处理,仅仅声明了抽象方法。Builder类的子类决定了用来编写文档的具体处理。


在示例程序中,我们定义了以下Builder类的子类。


TextBuilder类:使用纯文本(普通字符串)编写文档HTML.Builder类:使用HTML 编写文档


Director使用TextBuilder类时可以编写纯文本文档;使用HTMLBuilder类时可以编写HTML文档。


2.1 各个类之间的关系


各个类的功能:


1f4f6baa328fd10171b6593505277435.png

类图:

d91154eb5bcf1e4aec711d2e15b05b80.png


2.2 Builder类


Builder类是一个声明了编写文档的方法的抽象类。


public abstract class Builder {
    /**
     * 编写标题
     */
    public abstract void makeTitle(String title);
    /**
     * 编写字符串
     */
    public abstract void makeString(String str);
    /**
     * 编写条目
     * @param items
     */
    public abstract void makeItems(String[] items);
    /**
     * 完成编写
     */
    public abstract void close();
}


2.3 Director类


Director类使用Builder类中声明的方法来编写文档。


Director类的构造函数的参数是Builder类型的。但是实际上我们并不会将Builder类的实例作为参数传递给Director类。这是因为Builder类是抽象类,是无法生成其实例的。实际上传递给Director类的是Builder类的子类的实例。而正是这些Builder类的子类决定了编写出的文档的形式。


construct方法是编写文档的方法。调用这个方法后就会编写文档。construct方法中所使用的方法都是在Builder类中声明的方法( construct的意思是“构建”)。


public class Director {
    private Builder builder;
    //接收的参数是Builder类的子类
    public Director(Builder builder) {
        this.builder = builder;
    }
    /**
     * 编写文档
     */
    public void construct() {
        //标题
        builder.makeTitle("Greeting");
        //字符串
        builder.makeString("从早上至下午");
        //条目
        builder.makeItems(new String[]{
                "早上好。",
                "下午好。",
        });
        //其他字符串
        builder.makeString("晚上");
        //其他条目
        builder.makeItems(new String[]{
                "晚上好。",
                "晚安。",
                "再见。",
        });
        //完成
        builder.close();
    }
}


2.4 TextBuilder类


TextBuilder类是Builder类的子类,它的功能是使用纯文本编写文档,并以string返回结果。


public class TextBuilder extends Builder {
    //文档内容
    private StringBuffer buffer = new StringBuffer();
    /**
     * 纯文本的标题
     */
    @Override
    public void makeTitle(String title) {
        buffer.append("==============================\n");
        buffer.append("[" + title + "]\n");
        buffer.append("\n");
    }
    /**
     * 纯文本的字符串
     */
    @Override
    public void makeString(String str) {
        buffer.append('■' + str + "\n");
        buffer.append("\n");
    }
    /**
     * 纯文本的条目
     */
    @Override
    public void makeItems(String[] items) {
        for (int i = 0; i < items.length; i++) {
            buffer.append("  ▪" + items[i] + "\n");
        }
        buffer.append("\n");
    }
    /**
     * 完成文档
     */
    @Override
    public void close() {
        buffer.append("==============================\n");
    }
    /**
     * 完成的文档
     */
    public String getResult() {
        return buffer.toString();
    }
}


2.5 HTMLBuilder类


HTMLBuilder类也是Builder类的子类,它的功能是使用HTML编写文档,其返回结果是HTML文件的名字。


public class HTMLBuilder extends Builder{
    //文件名
    private String filename;
    private PrintWriter writer;
    /**
     * HTML文件的标题
     */
    @Override
    public void makeTitle(String title) {
        filename = title + ".html";
        try {
            new PrintWriter(new FileWriter(filename));
        } catch (IOException e) {
            e.printStackTrace();
        }
        writer.println("<html><head><title>" + title + "</title></head><body>");
        //输出标题
        writer.println("<h1>" + title + "</h1>");
    }
    /**
     * HTML文件中的字符串
     */
    @Override
    public void makeString(String str) {
        writer.println("<p>" + str + "</p>");
    }
    /**
     * html文件中的条目
     * @param items
     */
    @Override
    public void makeItems(String[] items) {
        writer.println("<ul>");
        for (int i = 0; i < items.length; i++) {
            writer.println("<li>" + items[i] + "</li>");
        }
        writer.println("</ul>");
    }
    /**
     * 完成文档
     */
    @Override
    public void close() {
        writer.println("</body></html>");
        writer.close();
    }
    /**
     * 编写好的文档
     */
    public String getResult() {
        return filename;
    }
}


2.6 Main类


Main类是Builder模式的测试程序。我们可以使用如下的命令来编写相应格式的文档:


java Main plain        编写纯文本文档
java Main html         编写HTML格式的文档


当我们在命令行中指定参数为plain的时候,会将TextBuilder类的实例作为参数传递至Director类的构造函数中;而若是在命令行中指定参数为html的时候,则会将HTMLBuilder类的实例作为参数传递至Director类的构造函数中。


由于TextBuilder和HTMLBuilder都是Builder的子类,因此Director仅仅使用Builder的方法即可编写文档。也就是说,Director并不关心实际编写文档的到底是TextBuilder还是HTMLBuilder。


正因为如此,我们必须在Builder中声明足够多的方法,以实现编写文档的功能,但并不包括TextBuilder和HTMLBuilder中特有的方法。


public class Main {
    public static void main(String[] args) {
        if (args.length != 1) {
            usage();
            System.exit(0);
        }
        if (args[0].equals("plain")) {
            TextBuilder textBuiler = new TextBuilder();
            Director director = new Director(textBuiler);
            director.construct();
            String result = textBuiler.getResult();
            System.out.println(result);
        } else if (args[0].equals("html")) {
            HTMLBuilder htmlBuilder = new HTMLBuilder();
            Director director = new Director(htmlBuilder);
            director.construct();
            String filename = htmlBuilder.getResult();
            System.out.println(filename + "文件编写完成。");
        } else {
            usage();
            System.exit(0);
        }
    }
    public static void usage() {
        System.out.println("Usage: java Main plain     编写纯文本文档");
        System.out.println("Usage: java Main html      编写HTML文档");
    }
}


2.7 运行结果


运行结果:


1d33f40f8505a3110dd0462733eccca9.png

生成的文档:

ab449885780b52264f9748f149013b27.png


三、相关的设计模式



3.1 Template Method模板方法模式


在 Builder模式中,Director角色控制 Builder角色。在Template Method模式中,父类控制子类。


这里的“控制”指的是方法的调用顺序的控制。在Builder模式中,Director 决定了Builder角色中方法的调用顺序,而在Template Method模式中,父类决定了子类方法的调用顺序。


设计模式学习(六):Template Method模板方法模式


3.2 Composite组合模式


有些情况下 Builder模式生成的实例构成了Composite模式。


3.3 Abstract Factory抽象工厂模式


Builder模式和Abstract Factory模式都用于生成复杂的实例。


设计模式学习(九):Abstract Factory抽象工厂模式


3.4 Facade外观模式


在Builder模式中,Director角色通过组合Builder角色中的复杂方法向外部提供可以简单生成实例的接口(API)(相当于示例程序中的construct方法)。


Facade模式中的Facade角色则是通过组合内部模块向外部提供可以简单调用的接口( API )。


四、拓展思路的要点



4.1谁知道什么


在面向对象编程中,“谁知道什么”是非常重要的。也就是说,我们需要在编程时注意哪个类可以使用哪个方法以及使用这个方法到底好不好。


请大家再回忆一下示例程序。Main类并不知道(没有调用)Builder类,它只是调用了Director类的construct方法。这样,Director类就会开始工作(Main类对此一无所知),并完成文档的编写。


另一方面,Director类知道Builder类,它调用Builder类的方法来编写文档,但是它并不知道它“真正”使用的是哪个类。也就是说它并不知道它所使用的类到底是TextBuilder类、HTMLBuilder类还是其他Builder类的子类。不过也没有必要知道,因为Director类只使用了Builder类的方法,而 Builder类的子类都已经实现了那些方法。


Director类不知道自己使用的究竟是Builder类的哪个子类也好。这是因为“只有不知道子类才能替换”。不论是将TextBuilder的实例传递给Director,还是将HTMLBuilder类的实例传递给Director,它都可以正常工作,原因正是Director类不知道Builder类的具体的子类。


正是因为不知道才能够替换,正是因为可以替换,组件才具有高价值。作为设计人员,我们必须时刻关注这种“可替换性”。


4.2 设计时能够决定的事情和不能决定的事情

在Builder类中,需要声明编辑文档(实现功能)所必需的所有方法。Director类中使用的方法都是Builder类提供的。因此,在Builder类中应当定义哪些方法是非常重要的。


而且,Builder类还必须能够应对将来子类可能增加的需求。在示例程序中,我们只编写了支持纯文本文档的子类和支持HTML文件的子类。但是将来可能还会希望能够编写其他形式(例如XXXX形式)的文档。那时候,到底能不能编写出支持xxxx形式的XXXXBuilder类呢?应该不需要新的方法吧?


虽然类的设计者无法准确地预测到将来可能发生的变化,但是我们还是有必要让设计出的类能够尽可能灵活地应对近期可能发生的变化。


4.3 代码的阅读方法和修改方法

在编程时,虽然有时需要从零开始编写代码,但更多时候我们都是在现有代码的基础上进行增加和修改。这时,我们需要先阅读现有代码。不过,只是阅读抽象类的代码是无法获取很多信息的(虽然可以从方法名中获得线索)。


让我们再回顾一下示例程序。即使理解了Builder抽象类,也无法理解程序整体。至少必须在阅读了Director的代码后才能理解Builder类的使用方法(Builder类的方法的调用方法)然后再去看看TextBuilder类和HTMLBuilder类的代码,就可以明白调用Builder类的方法后具体会进行什么样的处理。


如果没有理解各个类的角色就动手增加和修改代码,在判断到底应该修改哪个类时,就会很容易出错。例如,如果修改Builder类,那么就会对Director类中调用Builder类方法的地方和Builder类的子类产生影响。或是如果不小心修改了Director类,在其内部调用了TextBuilder类的特有的方法,则会导致其失去作为可复用组件的独立性,而且当将子类替换为HTMLBuilder时,程序可能会无法正常工作。


相关文章
|
26天前
|
设计模式 Java
【设计模式系列笔记】建造者模式
建造者模式是一种创建型设计模式,用于将复杂对象的构建与其表示分离,使构建过程可定制。关键元素包括产品类(定义要构建的对象)、建造者接口(定义构建方法)、具体建造者类(实现构建过程)和指导者类(负责构建过程)。通过建造者模式,客户端可以灵活地创建具有不同表示的复杂对象,提高代码的可读性和可维护性,尤其适用于构建过程复杂且包含多个可选部分的情况。
109 1
|
26天前
|
设计模式 安全 Java
构建未来应用:Java设计模式 - 建造者模式(Builder)在现代编程中的应用
【4月更文挑战第7天】建造者模式是提升代码质量的关键,尤其在复杂环境中。它分步骤构建对象,将构建与表示分离,适用于UI构建、数据模型组装、配置文件解析和网络请求构造等场景。最佳实践包括明确构建步骤、提供默认值、支持链式调用和确保线程安全。然而,过多步骤、不一致状态和性能问题是使用时需注意的问题。掌握建造者模式对于现代编程至关重要。
|
19天前
|
设计模式 算法 Java
【设计模式】JAVA Design Patterns——Builder(构造器模式)
【设计模式】JAVA Design Patterns——Builder(构造器模式)
|
19天前
|
设计模式 安全 Java
【JAVA学习之路 | 基础篇】单例设计模式
【JAVA学习之路 | 基础篇】单例设计模式
|
25天前
|
设计模式 存储 前端开发
JS的几种设计模式,Web前端基础三剑客学习知识分享,前端零基础开发
JS的几种设计模式,Web前端基础三剑客学习知识分享,前端零基础开发
|
26天前
|
设计模式 uml
【设计模式】建造者模式就是游戏模式吗?
【设计模式】建造者模式就是游戏模式吗?
18 0
|
26天前
|
设计模式 uml
大话设计模式(3)——造物者一般的建造者模式
大话设计模式(3)——造物者一般的建造者模式
19 1
大话设计模式(3)——造物者一般的建造者模式
|
26天前
|
设计模式 安全 Java
【设计模式学习】单例模式和工厂模式
【设计模式学习】单例模式和工厂模式
|
26天前
|
设计模式 JavaScript 前端开发
[设计模式Java实现附plantuml源码~创建型] 复杂对象的组装与创建——建造者模式
[设计模式Java实现附plantuml源码~创建型] 复杂对象的组装与创建——建造者模式
|
26天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式