建造者设计模式

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

}
相关推荐
晨米酱18 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机1 天前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机1 天前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤1 天前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴2 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
李广坤2 天前
工厂模式
设计模式