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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一、什么是Builder模式

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

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

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

二、Builder模式示例代码

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

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

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

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

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

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

2.1 各个类之间的关系

各个类的功能

类图

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 运行结果

运行结果

生成的文档

三、相关的设计模式

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时,程序可能会无法正常工作。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6