建造者设计模式

3. 建造者设计模式

3.1 原理

Builder 模式,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式

建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,"定制化"地创建不同的对象。

创建者模式主要包含以下四个角色:

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

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

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

java 复制代码
/**
 * 类描述:产品(Product),表示将要被构建的复杂对象。
 * 需求:创建一个复杂的HTML文档,它包含了标题、段落和图像等元素。我们可以使用创建者设计模式来构建HTML文档。
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/11/21 22:57
 */
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>";

    }
}    

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

java 复制代码
/**
 * 类描述:抽象创建者(Abstract Builder),定义构建产品的接口,通常包含创建和获取产品的方法。
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/11/21 23:01
 */
public abstract class HtmlDocumentBuilder {

    /**
     * 文档对象
     */
    protected HtmlDocument document;

    /**
     * 获取document文档对象
     *
     * @return
     */
    public HtmlDocument getDocument() {
        return document;
    }

    /**
     * 创建document文档对象
     */
    public void createNewHtmlDocument() {
        document = new HtmlDocument();
    }

    /**
     * 构建文档对象的header
     */
    public abstract void buildHeader();

    /**
     * 构建文档对象的body
     */
    public abstract void buildBody();

    /**
     * 构建文档对象的footer
     */
    public abstract void buildFooter();
}

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

java 复制代码
/**
 * 类描述:具体创建者(Concrete Builder),实现抽象创建者定义的接口,为产品的各个部分提供具体实现。
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/11/21 23:04
 */
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");
    }
}

指挥者(Director)类 - HtmlDirector:

java 复制代码
public class HtmlDirector {
    /**
     * 构建者
     */
    private HtmlDocumentBuilder builder;

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

    /**
     * 构建文档对象
     */
    public void constructDocument() {
        builder.createNewHtmlDocument();
        builder.buildHeader();
        builder.buildBody();
        builder.buildFooter();
    }

    /**
     * 获取document对象
     *
     * @return
     */
    public HtmlDocument getDocument() {
        return builder.getDocument();
    }
}

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

java 复制代码
/**
 * 类描述:构建者设计模式的测试
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/11/21 23:16
 */
@Slf4j
public class BuilderPatternTest {
    @Test
    public void testHtmlDocumentBuilder() {
        HtmlDocumentBuilder htmlDocumentBuilder = new ArticleHtmlDocumentBuilder();
        HtmlDirector director = new HtmlDirector(htmlDocumentBuilder);
        director.constructDocument();
        HtmlDocument htmlDocument = director.getDocument();
        log.info(">>> Constructed HTML Document: \n {}", htmlDocument.toString());
    }
}

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

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

java 复制代码
/**
 * 类描述:产品(Product),表示将要被构建的复杂对象。
 * 需求:创建一个复杂的HTML文档,它包含了标题、段落和图像等元素。我们可以使用创建者设计模式来构建HTML文档。
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/11/21 22:57
 */
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 Builder builder() {
        return new Builder();
    }

    public static class Builder {
        protected HtmlDocument document;

        private 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文档对象:

java 复制代码
/**
 * 类描述:构建者设计模式的测试
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/11/21 23:16
 */
@Slf4j
public class BuilderPatternTest {
    @Test
    public void testHtmlDocumentBuilder2() {
        HtmlDocument htmlDocument = HtmlDocument.builder().addHeader("This is the header")
                .addBody("This is body")
                .addFooter("This is footer")
                .build();
        log.info(">>> Constructed HTML Document: \n {}", htmlDocument.toString());

    }
}

3.2 为什么需要建造者模式

(1) 根据复杂的配置项进行定制化构建。

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

java 复制代码
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<Class<? extends Cache>>();
  }

  public CacheBuilder implementation(Class<? extends Cache> implementation) {
    this.implementation = implementation;
    return this;
  }

  public CacheBuilder addDecorator(Class<? extends Cache> decorator) {
    if (decorator != null) {
      this.decorators.add(decorator);
    }
    return this;
  }

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

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

  public CacheBuilder readWrite(boolean readWrite) {
    this.readWrite = readWrite;
    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);
    // issue #352, do not apply decorators to custom caches
    // 根据配置的装饰器对原有缓存进行增强,如增加淘汰策略等
    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;
  }
}    

我们总结这个案例中的几个特点:

  • 参数有必填项id,有很多可选填的内容,通常必选项id通过构造器传入,可选项通过方法传递。
  • 真正的构建过程需要调用build方法,构建时需要根据已配置的成员变量的内容选择合适的装饰器,对目标cache进行增强。

(2) 实现不可变对象

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

java 复制代码
/**
 * 类描述:实现不可变对象
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/11/26 21:21
 */
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 Builder builder() {
        return new Builder();
    }

    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 类具有三个属性: name 、age 和 address 。这些属性都是 final 修饰,一旦设置就不能更改。ImmutablePerson 的构造函数是私有的且没有set方法,外部无法直接创建该类的实例。需要使用内部的 Builder 类要创建一个 ImmutablePerson 实例,通过连续调用 Builder 类的方法,来为ImmutablePerson 设置属性。最后调用 build() 方法,创建一个具有指定属性的不可变 ImmutablePerson 实例。

3.3 源码应用

创建者设计模式在源码中有广泛的使用:

(1)jdk中的StringBuilder和StringBuffer,他们的实现不是完全按照标准的创建者设计模式设计,但也是一样的思想。

这两个类用于构建和修改字符串。它们实现了创建者模式,允许通过方法链来修改字符串。这些类在性能上优于 String 类,因为它们允许在同一个对象上执行多次修改,而不需要每次修改都创建一个新的对象。

java 复制代码
StringBuilder builder = new StringBuilder();
builder.append("Hello").append(" ").append("World!");
String result = builder.toString();

(2)在ssm源码中使用创建者设计模式,如Spring中的BeanDefinitionBuilder 类,mybatis中SqlSessionFactoryBuilder 、

XMLConfigBuilder 、XMLMapperBuilder 、XMLStatementBuilder 、CacheBuilder 等。

(3)使用lombok实现创建者设计模式

Lombok 是一个 Java 库,它可以简化代码,提高开发效率,尤其是在实现模式和生成常用方法(例如 getter、setter、equals、hashCode 和 toString)时。以下是一个使用 Lombok 的创建者设计模式的例子:

首先在项目中引入了 Lombok 依赖:

xml 复制代码
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.12</version>
	<scope>provided</scope>
</dependency>

创建一个类, 使用lombok的相关注解实现建造者模式:

java 复制代码
/**
 * 类描述:使用Lombok构建实例,查看编译后的class文件,会自动生成构建对象的代码(使用的构建者模式)
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/11/20 23:19
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class Person {

    private String name;
    private Integer age;
}

反编译后的Person类:

java 复制代码
package cn.itcast.designPatterns.builder;

public class Person {
    private String name;
    private Integer age;

    public static PersonBuilder builder() {
        return new PersonBuilder();
    }

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public Integer getAge() {
        return this.age;
    }

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

    public void setAge(Integer age) {
        this.age = age;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof Person)) {
            return false;
        } else {
            Person other = (Person)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                Object this$name = this.getName();
                Object other$name = other.getName();
                if (this$name == null) {
                    if (other$name != null) {
                        return false;
                    }
                } else if (!this$name.equals(other$name)) {
                    return false;
                }

                Object this$age = this.getAge();
                Object other$age = other.getAge();
                if (this$age == null) {
                    if (other$age != null) {
                        return false;
                    }
                } else if (!this$age.equals(other$age)) {
                    return false;
                }

                return true;
            }
        }
    }

    protected boolean canEqual(Object other) {
        return other instanceof Person;
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $name = this.getName();
        result = result * 59 + ($name == null ? 43 : $name.hashCode());
        Object $age = this.getAge();
        result = result * 59 + ($age == null ? 43 : $age.hashCode());
        return result;
    }

    public String toString() {
        return "Person(name=" + this.getName() + ", age=" + this.getAge() + ")";
    }
	// 内部类建造者
    public static class PersonBuilder {
        private String name;
        private Integer age;

        PersonBuilder() {
        }

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

        public PersonBuilder age(Integer age) {
            this.age = age;
            return this;
        }

        public Person build() {
            return new Person(this.name, this.age);
        }

        public String toString() {
            return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ")";
        }
    }
}

测试lombok创建的建造者模式实现的类

java 复制代码
@Test
public void testPerson() {
	Person person = builder().name("crysw")
			.age(23)
			.build();

	person.setName("panda");
	person.setAge(22);
	log.info(">>> person: {}", person);

}
相关推荐
zh路西法2 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(二):从FSM开始的2D游戏角色操控底层源码编写
c++·游戏·unity·设计模式·状态模式
夏旭泽3 小时前
设计模式-备忘录模式
设计模式·备忘录模式
蓝染-惣右介3 小时前
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
java·设计模式
捕鲸叉7 小时前
C++软件设计模式之类型模式和对象型模式
开发语言·c++·设计模式
诸葛悠闲7 小时前
设计模式——组合模式
设计模式·组合模式
诸葛悠闲7 小时前
设计模式——装饰模式
设计模式
西岭千秋雪_7 小时前
设计模式の中介者&发布订阅&备忘录模式
java·观察者模式·设计模式·中介者模式·备忘录模式
捕鲸叉7 小时前
C++软件设计模式之代理(Proxy)模式
c++·设计模式
思忖小下8 小时前
梳理你的思路(从OOP到架构设计)_介绍GoF设计模式
设计模式·架构·eit