Builder 模式,也叫生成器模式。创建者模式主要包含以下四个角色:

  1. 产品(Product):表示将要被构建的复杂对象。
  2. 抽象创建者(Abstract Builder):定义构建产品的接口,通常包含创建和获取产品的方法。
  3. 具体创建者(Concrete Builder):实现抽象创建者定义的接口,为产品的各个部分提供具体实现。
  4. 指挥者(Director):负责调用具体创建者来构建产品的各个部分,控制构建过程。

假设我们需要创建一个复杂的HTML文档,它包含了标题、段落和图像等元素。可以使用创建者设计模式来构建HTML文档。

1、产品(Product)类 - HTML文档(HtmlDocument):

public class HtmlDocument {
    private String header = "";
    private String body = "";
    private String footer = "";

    public void addHeader(String header) {
        this.header = header;
    }

    public void addBody(String body) {
        this.body = body;
    }

    public void addFooter(String footer) {
        this.footer = footer;
    }

    @Override
    public String toString() {
        return "<html><head>" + header + "</head><body>" + body + "</body><footer>" + footer + "</footer></html>";
    }
}

2、抽象创建者(Abstract Builder)类 - HtmlDocumentBuilder:

public abstract class HtmlDocumentBuilder {
    protected HtmlDocument document;

    public HtmlDocument getDocument() {
        return document;
    }

    public void createNewHtmlDocument() {
        document = new HtmlDocument();
    }

    public abstract void buildHeader();
    public abstract void buildBody();
    public abstract void buildFooter();
}

3、具体创建者(Concrete Builder)类 - ArticleHtmlDocumentBuilder:

public class ArticleHtmlDocumentBuilder extends HtmlDocumentBuilder {
    @Override
    public void buildHeader() {
        document.addHeader("Article Header");
    }

    @Override
    public void buildBody() {
        document.addBody("Article Body");
    }

    @Override
    public void buildFooter() {
        document.addFooter("Article Footer");
    }
}

4、指挥者(Director)类 - HtmlDirector:

public class HtmlDirector {
    private HtmlDocumentBuilder builder;

    public HtmlDirector(HtmlDocumentBuilder builder) {
        this.builder = builder;
    }

    public void constructDocument() {
        builder.createNewHtmlDocument();
        builder.buildHeader();
        builder.buildBody();
        builder.buildFooter();
    }

    public HtmlDocument getDocument() {
        return builder.getDocument();
    }
}

使用创建者设计模式来构建一个HTML文档对象:

public class Main {
    public static void main(String[] args) {
        HtmlDocumentBuilder articleBuilder = new ArticleHtmlDocumentBuilder();
        HtmlDirector director = new HtmlDirector(articleBuilder);

        director.constructDocument();
        HtmlDocument document = director.getDocument();

        System.out.println("Constructed HTML Document: \n" + document);
    }
}

在这个例子中创建了一个表示HTML文档的产品类(HtmlDocument),一个抽象的创建者类(HtmlDocumentBuilder),一个具体的创建者类(ArticleHtmlDocumentBuilder)和一个指挥者类(HtmlDirector)。当需要创建一个新的HTML文档对象时,可以使用指挥者类来控制构建过程,从而实现了将构建过程与表示过程的分离。

以上是一个创建者设计模式的标准写法,事实,在工作中往往不会写的这么复杂,为了创建一个对象,创建了很多辅助的类,总觉得不太合适,在这个案例中,可以使用内部类来简化代码,以下是修改后的代码(甚至我们还移除了抽象层):

public class HtmlDocument {
    private String header = "";
    private String body = "";
    private String footer = "";

    public void addHeader(String header) {
        this.header = header;
    }

    public void addBody(String body) {
        this.body = body;
    }

    public void addFooter(String footer) {
        this.footer = footer;
    }

    @Override
    public String toString() {
        return "<html><head>" + header + "</head><body>" + body + "</body><footer>" + footer + "</footer></html>";
    }

    public static class Builder {
        protected HtmlDocument document;

        public Builder() {
            document = new HtmlDocument();
        }

        public Builder addHeader(String header) {
            document.addHeader(header);
            return this;
        }

        public Builder addBody(String body) {
            document.addBody(body);
            return this;
        }

        public Builder addFooter(String footer) {
            document.addFooter(footer);
            return this;
        }

        public HtmlDocument build() {
            return document;
        }
    }
}

现在使用以下代码来创建一个HTML文档对象:

public class Main {
    public static void main(String[] args) {
        HtmlDocument.ArticleBuilder builder = new HtmlDocument.ArticleBuilder();
        HtmlDocument document = builder.addHeader("This is the header")
                                       .addBody("This is the body")
                                       .addFooter("This is the footer")
                                       .build();

        System.out.println("Constructed HTML Document: \n" + document);
    }
}

在这个修改后的例子中,将创建者类(Builder)作为HTML文档类(HtmlDocument)的内部类。这样做可以让代码更加紧凑。此外,使用了一种流式接口(Fluent Interface),使得在客户端代码中创建HTML文档对象更加简洁。

当然有些人看了这个案例,已经会觉得

为什么需要建造者模式

需求一、根据复杂的配置项进行定制化构建

首先,先看一个mybaits中经典的案例,这个案例中使用了装饰器和创建者设计模式:

public class CacheBuilder {
    private final String id;
    private Class<? extends Cache> implementation;
    private final List<Class<? extends Cache>> decorators;
    private Integer size;
    private Long clearInterval;
    private boolean readWrite;
    private Properties properties;
    private boolean blocking;

    public CacheBuilder(String id) {
        this.id = id;
        this.decorators = new ArrayList<>();
    }

    public CacheBuilder size(Integer size) {
        this.size = size;
        return this;
    }

    public CacheBuilder clearInterval(Long clearInterval) {
        this.clearInterval = clearInterval;
        return this;
    }

    public CacheBuilder blocking(boolean blocking) {
        this.blocking = blocking;
        return this;
    }

    public CacheBuilder properties(Properties properties) {
        this.properties = properties;
        return this;
    }

    public Cache build() {
        setDefaultImplementations();
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);
        // 根据配置的装饰器对原有缓存进行增强,如增加淘汰策略等
        if (PerpetualCache.class.equals(cache.getClass())) {
            for (Class<? extends Cache> decorator : decorators) {
                cache = newCacheDecoratorInstance(decorator, cache);
                setCacheProperties(cache);
            }
            cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
            cache = new LoggingCache(cache);
        }
        return cache;
    }
}

这个案例中的几个特点:

1、参数有必填项id,有很多可选填的内容,通常必选项id通过构造器传入,可选项通过方法传递。

2、真正的构建过程需要调用build方法,构建时需要根据已配置的成员变量的内容选择合适的装饰器,对目标cache进行增强。

需求二、实现不可变对象

创建者设计模式(Builder Design Pattern)可以实现不可变对象,即一旦创建完成,对象的状态就不能改变。这有助于保证对象的线程安全和数据完整性。下面是一个使用创建者设计模式实现的不可变对象的Java示例:

public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final String address;

    private ImmutablePerson(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.address = builder.address;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getAddress() {
        return address;
    }

    public static class Builder {
        private String name;
        private int age;
        private String address;

        public Builder() {
        }

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Builder setAddress(String address) {
            this.address = address;
            return this;
        }

        public ImmutablePerson build() {
            return new ImmutablePerson(this);
        }
    }
}

在这个例子中,ImmutablePerson 类具有三个属性:nameage 和 address。这些属性都是 final 的,一旦设置就不能更改。ImmutablePerson 的构造函数是私有的,外部无法直接创建该类的实例。要创建一个 ImmutablePerson 实例,需要使用内部的 Builder 类。通过连续调用 Builder 类的方法,我们可以为 ImmutablePerson 设置属性。最后,通过调用 build() 方法,创建一个具有指定属性的不可变 ImmutablePerson 实例。

实际上,使用建造者模式创建对象,还能避免对象存在无效状态。如定义了一个长方形类,如果不使用建造者模式,采用先创建后 set 的方式,那就会导致在第一个 set 之后,对象处于无效状态。具体代码如下所示:

Rectangle r = new Rectange(); // r is invalid
r.setWidth(2); // r is invalid
r.setHeight(3); // r is valid

为了避免这种无效状态的存在,就需要使用构造函数一次性初始化好所有的成员变量。如果构造函数参数过多,就需要考虑使用建造者模式,先设置建造者的变量,然后再一次性地创建对象,让对象一直处于有效状态。

实际上,如果我们并不是很关心对象是否有短暂的无效状态,也不是太在意对象是否是可变的。比如,对象只是用来映射数据库读出来的数据,那我们直接暴露 set() 方法来设置类的成员变量值是完全没问题的。而且,使用建造者模式来构建对象,代码实际上是有点重复的。


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