【23种设计模式】建造者模式【⭐⭐⭐】

2023-09-13 01:06:15

个人主页金鳞踏雨

个人简介:大家好,我是金鳞,一个初出茅庐的Java小白

目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作

我的博客:这里是CSDN,是我学习技术,总结知识的地方。希望和各位大佬交流,共同进步 ~

比较简单,但是很经常用!

个人感悟:

为什么会有这种设计模式?往往是因为语言或者框架本身的缺陷而导致的。这个语言或者框架本身就不支持这种开发形式,我们必须使用一种开发"套路"来解决这个问题!

那为什么有一些设计模式我们在开发中很少见呢?因为本身一些框架就提供了这些功能!我们使用起来就很简单,所以就没有那么重要了!

一、原理

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

实际上,建造者模式的原理和代码实现非常简单,掌握起来并不难,难点在于应用场景。比如,你有没有考虑过这样几个问题:直接使用构造函数或者配合 set 方法就能创建对象,为什么还需要建造者模式来创建呢?建造者模式和工厂模式都可以创建对象,那它们两个的区别在哪里呢,话不多说,我们直接来学习:

角色

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

示例代码

我们考虑一个文档编辑器的例子。假设我们需要创建一个复杂的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.Builder builder = new HtmlDocument.Builder();
        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)的内部类

这样做可以让代码更加紧凑。此外,我们使用了一种流式接口,使得在客户端代码中创建HTML文档对象更加简洁。

二、为什么需要建造者模式

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

首先,我们先看一个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() {
        // 使用了装饰器模式,添加 LruCache
        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进行增强。

2. 实现不可变对象

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

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 类具有三个属性:name、age 和 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() 方法来设置类的成员变量值是完全没问题的。而且,使用建造者模式来构建对象,代码实际上是有点重复的。

小结

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

我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。

  • 如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。
  • 如果我们把必填属性通过 set() 方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放了。
  • 如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合 set() 方法的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。
  • 如果我们希望创建不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值,要实现这个功能,我们就不能在类中暴露 set() 方法。构造函数配合 set() 方法来设置属性值的方式就不适用了。

建造者与工厂模式有何区别?

实际上,其实不用非得把工厂模式、建造者模式分得那么清楚,我们需要知道的是,每个模式为什么这么设计,能解决什么问题。只有了解了这些最本质的东西,我们才能不生搬硬套,才能灵活应用,甚至可以混用各种模式创造出新的模式,来解决特定场景的问题。

三、源码应用

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

1、JDK中,如StringBuilderStringBuffer,他们的实现不是完全按照标准的创建者设计模式设计,但也是一样的思想:

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

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 简单地实现创建者设计模式,可以使用 @Builder 注解。自动生成创建者类和相关方法。

首先,引入 Lombok 库。

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

然后,创建一个使用 Lombok 的创建者设计模式的类:

@Getter
@ToString
@Builder
public class Person {
    private String name;
    private int age;
    private String address;
}

在上面的示例中,我们使用了 @Builder 注解来自动生成创建者类和相关方法。此外,我们还使用了 @Getter 注解来自动生成 getter 方法,以及 @ToString 注解来自动生成 toString 方法。

自动生成的创建者类创建 Person 对象

Person person = Person.builder()
    .name("John Doe")
    .age(30)
    .address("123 Main St")
    .build();

通过 Lombok,可以轻松地实现创建者设计模式,减少样板代码,提高代码可读性。

文章到这里就结束了,如果有什么疑问的地方,可以在评论区指出~

希望能和大佬们一起努力,诸君顶峰相见

再次感谢各位小伙伴儿们的支持!!!

更多推荐

神经网络 05(损失函数)

一、损失函数在深度学习中,损失函数是用来衡量模型参数的质量的函数,衡量的方式是比较网络输出和真实输出的差异,损失函数在不同的文献中名称是不一样的,主要有以下几种命名方式:损失函数(lossfunction)代价函数(costfunction)目标函数(objectivefunction)误差函数(errorfuncti

【Vue】模板语法,事件处理器及综合案例、自定义组件、组件通信

一、事件处理器我们之前事件监听可以使用v-on指令1、事件修饰符在Vue中我们通过由点(.)表示的指令后缀来调用修饰符,比如:.stop:阻止事件冒泡。当事件触发时,该修饰符将停止事件进一步冒泡到父元素。相当于调用了event.stopPropagation().prevent:阻止默认事件。默认情况下,某些元素会有默

python正则表达(06)

python正则表达(06)文章目录python正则表达(06)1正则表达式概念2正则的三个基础方法2.1match、search、findall三个基础方法2.2re.match()函数2.2.1re.match(匹配规则,被匹配字符串)2.2.2验证是否开头匹配,match是匹配开头,后面的是不匹配2.3re.se

SpringCloud Eureka搭建会员中心服务提供方-集群

😀前言本篇博文是关于SpringCloudEureka搭建会员中心服务提供方-集群,希望你能够喜欢🏠个人主页:晨犀主页🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意是我的动力😉😉💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,感谢大家的观看🥰如果文章有什么需要改进的地方

Java基于微信小程序的电影交流平台

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W+、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌文章目录第一章:简介第二章、开发环境:后端:前端:数据库:三、系统详细设计3.4.1数据库概念结构设计第四章系统功能的具体实现4.1小程序端4.2管理员

java基于微信小程序的讲座预约系统的研究与实现

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W+、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌文章目录1简介2技术栈第三章系统分析3.1初步需求分析3.2系统用例分析3.2.1公告管理用例分析3.2.2系统管理用例分析3.2.3学生信息用例分析3

MyBatis快速入门

Mybatis概述Mybatis概念MyBatis是一款优秀的持久层框架,用于简化JDBC开发MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为MyBatis。2013年11月迁移到Github官网:ht

nginx配置指南

nginx.conf配置找到Nginx的安装目录下的nginx.conf文件,该文件负责Nginx的基础功能配置。配置文件概述Nginx的主配置文件(conf/nginx.conf)按以下结构组织:配置块功能描述全局块与Nginx运行相关的全局设置events块与网络连接有关的设置http块代理、缓存、日志、虚拟主机等

Android9底部导航栏出现空白按钮问题分析

Android9底部导航栏出现空白按钮问题分析底部导航栏的初始化进入NavigationBarView初始化:进入NavigationBarView的onFinishInflater进入NavigationBarInflaterViewNavigationBarInflaterView加载单个的button回到Navi

XUI - 一个简洁而优雅的Android原生UI框架

官网GitHub-xuexiangjys/XUI:💍AsimpleandelegantAndroidnativeUIframework,freeyourhands!(一个简洁而优雅的Android原生UI框架,解放你的双手!)XUI|💍AsimpleandelegantAndroidnativeUIframewor

【算法】相向双指针

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。推荐:kuan的首页,持续学习,不断总结,共同进步,活到老学到老导航檀越剑指大厂系列:全面总结java核心技术点,如集合,jvm,并发编程redis,kaf

热文推荐