《图解设计模式》之建造者模式


建造者模式——组装复杂的实例

具有复杂结构时,需要首先建造组成这个结构的各个部分,然后分阶段将它们组装起来。

示例

示例程序中一共出现5个类:Builder类,Director类,TextBuilder类,HTMLBuilder类以及Main类

使用Builder类编写“文档”,编写的文档具有以下结构。

  • 含有一个标题
  • 含有几个字符串
  • 含有条目项目

Builder类是抽象类,其中定义了决定文档结构的抽象方法,其子类TextBuilder类,HTMLBuilder类决定了编写文档的具体方法。具体文档则由Director类使用Builder类编写。

示例程序的类图

Builder类

public abstract class Builder {
    public abstract void makeTitle(String title);

    public abstract void makeString(String str);

    public abstract void makeItems(String[] items);

    public abstract void close();
}

Director类

public class Director {
    private 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();
    }
}

TextBuilder类

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();
    }
}

HTMLBuilder类

public class HTMLBuilder extends Builder {
    private String fileName;
    private PrintWriter writer;

    @Override
    public void makeTitle(String title) {
        fileName = title + ".html";
        try {
            writer = new PrintWriter(new FileWriter(fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        writer.println("" + title + "");
        writer.println("

" + title + "

"); } @Override public void makeString(String str) { writer.println("

" + str + "

"); } @Override public void makeItems(String[] items) { writer.println("
    "); for (int i = 0; i < items.length; i++) { writer.println("
  • " + items[i] + "
  • "); } writer.println("
"); } @Override public void close() { writer.println(""); writer.close(); } public String getResult() { return fileName; } }

Main类

public class Main {
    public static void main(String[] args) {
        if (args.length != 1) {
            usage();
            System.exit(0);
        }
        if (args[0].equals("plain")) {
            TextBuilder textbuilder = new TextBuilder();
            Director director = new Director(textbuilder);
            director.construct();
            String result = textbuilder.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文档");

    }
}

Main项目启动时,配置args值为“plain”或“html”来分别调用TextBuilder类、HTMLBuilder类

建造者模式中登场的角色

  • Builder(建造者)

    Builder角色负责定义用于生成实例的接口(API)。Builder角色中准备了用于生成实例的方法。示例程序中,由Builder类扮演此角色。

  • ConcreteBuilder(具体的建造者)

    ConcreteBuilder角色负责实现Builder角色的接口(API)。这里定义了在生成实例时实际被调用的方法。此外,在ConcreteBuilder角色中还定义了获取最终生成结果的方法。在示例程序中,由TextBuilder类,HTMLBuilder类扮演此角色。

  • Director(建工)

    Director角色负责使用Builder角色的接口(API)来生成实例。它并不依赖于ConcreteBuilder角色。为了确保不论ConcreteBuilder角色被如何定义,Director角色都能正常工作,它只调用在Builder角色中被定义的方法。在示例程序中,由Director类扮演此角色。

  • Client(使用者)

    该角色由Main类扮演

    建造者模式的类图

### 建造者模式的时序图

要点

在面对对象编程中,“谁知道什么”是非常重要的。

现在再重新看一遍示例程序,Main类并不知道Builder类,它只是调用了Director类的construct方法。,Director类就会开始“编写文档”,Main对此一无所知。

另外,Director类只知道Builder类,却并不知道“真正”调用了Builder类的哪个子类。也就是说它并不知道调用了TextBuilder类还是HTMLBuilder类亦或是其他子类。

其Director类不知道究竟使用了Builder类的哪个子类是好事,为什么这么说呢?因为“只有不知道子类才能随意替换”。不论是将TextBuilder类实例传递给Director还是将HTMLBuilder类传递给它,它都能正常运行。正因如此,才能够替换。而可以替换的组件才具有更高的价值。

作为开发人员,我们必须时刻关注这种”可替代性“。