一天一个设计模式(五):建造者模式,构建优美的Java对象

createh52个月前 (02-05)技术教程10

建造者模式(Builder Pattern)是一种创建型设计模式,其主要作用是将一个复杂对象的构建过程与它的表现分离开。这种分离意味着同样的构建过程可以创建不同的表现,同时,不同的构建过程也可以用来创建相同的表现。这使得代码更加灵活、可维护和可扩展。

一、原理

建造者模式关注的是如何构建复杂对象,它将复杂对象的构建过程分解为多个简单对象的构建步骤,从而达到分步构建复杂对象的目的。通常情况下,这些简单对象的构建顺序是固定的,但由于使用了建造者模式,所以可以采用不同的顺序来构建出不同的对象。

建造者模式中包含以下角色:

  • Builder (抽象建造者):定义了构建复杂对象所需要的各个部件的抽象接口。
  • ConcreteBuilder (具体建造者):实现了Builder接口,实现各个部件的具体构建与装配方法。
  • Product (产品角色):具体构建出来的复杂对象,由多个简单对象组成。
  • Director (指挥者):调用具体建造者来创建复杂对象的各个部分,并按照特定的顺序装配这些部分,最终构建完成整个复杂对象。

二、示例代码

假设我们要构建一个简单的计算机,计算机由 CPU、内存、硬盘和显示器等组成。为了使用建造者模式,我们必须将计算机的构建过程分解为多个步骤,并且使用 Builder 接口定义这些步骤的规范。具体步骤如下:

1、创建抽象建造者接口 Builder,其中定义了创建 CPU、内存、硬盘和显示器等组件的方法。

public interface Builder {
    void buildCPU(String cpu);
    void buildMemory(String memory);
    void buildHardDisk(String hardDisk);
    void buildDisplay(String display);
}

2、创建具体建造者类 ConcreteBuilder,它实现了 Builder 接口,并在其中对每个组件的属性进行设置。

public class ConcreteBuilder implements Builder {
    private Computer computer = new Computer();
    
    public void buildCPU(String cpu) {
        computer.setCPU(cpu);
    }
    
    public void buildMemory(String memory) {
        computer.setMemory(memory);
    }
    
    public void buildHardDisk(String hardDisk) {
        computer.setHardDisk(hardDisk);
    }
    
    public void buildDisplay(String display) {
        computer.setDisplay(display);
    }
    
    public Computer getResult() {
        return computer;
    }
}

3、创建指挥者类 Director,它负责控制计算机的构建过程,并使用 Builder 接口定义的方法来设置每个组件的属性。

public class Director {
    public void construct(Builder builder) {
        builder.buildCPU("i7");
        builder.buildMemory("16GB");
        builder.buildHardDisk("1TB");
        builder.buildDisplay("21 inch");
    }
}

4、创建产品类 Computer,这里我们只需要提供一些简单的 getter 和 setter 方法即可。

public class Computer {
    private String cpu;
    private String memory;
    private String hardDisk;
    private String display;
    
    public String getCPU() {
        return cpu;
    }
    
    public void setCPU(String cpu) {
        this.cpu = cpu;
    }
    
    public String getMemory() {
        return memory;
    }
    
    public void setMemory(String memory) {
        this.memory = memory;
    }
    
    public String getHardDisk() {
        return hardDisk;
    }
    
    public void setHardDisk(String hardDisk) {
        this.hardDisk = hardDisk;
    }
    
    public String getDisplay() {
        return display;
    }
    
    public void setDisplay(String display) {
        this.display = display;
    }
}

最终,我们可以通过如下代码使用建造者模式创建一个计算机对象:

public static void main(String[] args) {
    Director director = new Director();
    ConcreteBuilder builder = new ConcreteBuilder();
    director.construct(builder);
    Computer computer = builder.getResult();
}

当我们需要构建多种类型的计算机时,只需要编写不同的具体建造者实现即可。通过使用建造者模式,我们将复杂对象的构建过程和表示分离开来,使得代码更加清晰、易于维护,同时也能够提高代码的可扩展性和灵活性。

三、建造者模式的优缺点

建造者模式具有以下优点:

优点:

1、将对象的创建过程封装起来,使得客户端代码与对象的创建过程分离,从而提高系统的可维护性、可扩展性;

2、可以控制对象的创建过程,使得对象的构建过程更加灵活,可以根据不同的需求创建不同的对象;

3、可以通过建造者模式创建一些复杂的对象,同时又避免了大量重复的代码,提高了代码复用性。

缺点:

建造者模式需要定义多个角色,包括具体产品、抽象建造者、具体建造者等,因此会增加系统的复杂度;

四、SpringBoot中的建造者模式

SpringBoot 中的许多配置都用到了建造者模式,最常见的就是使用 Builder 配置类来构建 DataSource 对象。

在 SpringBoot 中,我们有时会使用 Spring 提供的 org.springframework.boot.jdbc.DataSourceBuilder 类来构建一个数据源对象。 DataSourceBuilder 中提供了许多静态方法用于创建不同类型的数据源,例如:create()、create(ClassLoader classLoader)、create(ClassLoader classLoader, Environment environment) 等等。这些静态方法都返回了一个 DataSourceBuilder 类型的对象,在这个对象的基础上,我们又可以继续调用其它的方法来设置数据源的各种属性。

举例来说,如果要使用 Hikari 数据源,则可以通过如下代码来创建一个 HikariDataSource 对象:

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.jdbc.DataSourceBuilder;

public class DataSourceExample {
    public static void main(String[] args) {
        HikariDataSource dataSource = DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .url("jdbc:h2:mem:test")
                .username("sa")
                .password("")
                .build();
    }
}

在上述代码中,我们首先通过 DataSourceBuilder.create() 静态方法创建一个数据源的 Builder 对象,并且使用 type(HikariDataSource.class) 方法指定了数据源类型为 HikariDataSource。然后,我们又可以继续使用其它的方法来设置数据源的各种属性,例如 url()、username()、password() 等等,最终通过 build() 方法来构建出一个数据源对象。

在这个过程中,我们可以看到 DataSourceBuilder 中使用了建造者模式来构建一个数据源对象。具体来说,我们可以将 Builder 视为一个抽象建造者,DataSourceBuilder 类则是其具体建造者的实现,HikariDataSource 则是最终构建出来的产品。通过这种方式,我们可以将创建产品的过程和表示分离开来,使得代码更加清晰、易于维护,同时也能够提高代码的可扩展性和灵活性。

以下是 DataSourceBuilder 部分源代码解释:

public final class DataSourceBuilder<T extends DataSource> {

    // 支持的数据源类型数组
    private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] {
            "com.zaxxer.hikari.HikariDataSource",
            "org.apache.tomcat.jdbc.pool.DataSource",
            "org.apache.commons.dbcp2.BasicDataSource"
    };

    // 数据源类型
    private Class<? extends DataSource> type;
    // 类加载器
    private ClassLoader classLoader;
    // 属性键值对
    private Map<String, String> properties = new HashMap<>();

    /**
     * 静态工厂方法,创建一个默认的 DataSourceBuilder 对象
     */
    public static DataSourceBuilder<?> create() {
        return new DataSourceBuilder<>(null);
    }

    /**
     * 静态工厂方法,创建一个指定类加载器的 DataSourceBuilder 对象
     */
    public static DataSourceBuilder<?> create(ClassLoader classLoader) {
        return new DataSourceBuilder<>(classLoader);
    }

    /**
     * 私有构造函数,创建一个指定类加载器的 DataSourceBuilder 对象
     */
    private DataSourceBuilder(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

   ......
   
    /**
     * 构建数据源对象
     */
    @SuppressWarnings("unchecked")
    public T build() {
        Class<? extends DataSource> type = getType();
        DataSource result = BeanUtils.instantiateClass(type);
        maybeGetDriverClassName();
        bind(result);
        return (T) result;
    }

    /**
     * 设置数据源类型
     * @param type 数据源类型
     */
    @SuppressWarnings("unchecked")
    public <D extends DataSource> DataSourceBuilder<D> type(Class<D> type) {
        this.type = type;
        return (DataSourceBuilder<D>) this;
    }

    /**
     * 设置数据源 URL
     * @param url 数据源 URL
     */
    public DataSourceBuilder<T> url(String url) {
        this.properties.put("url", url);
        return this;
    }

    /**
     * 设置数据源驱动类名
     * @param driverClassName 数据源驱动类名
     */
    public DataSourceBuilder<T> driverClassName(String driverClassName) {
        this.properties.put("driverClassName", driverClassName);
        return this;
    }

    /**
     * 设置数据源用户名
     * @param username 数据源用户名
     */
    public DataSourceBuilder<T> username(String username) {
         this.properties.put("username", username);
         return this;
     }

    /**
     * 设置数据源密码
     * @param password 数据源密码
     */
    public DataSourceBuilder<T> password(String password) {
        this.properties.put("password", password);
        return this;
    }

    ......
}

相关文章

浅析Java反射_java反射的用处

前言上篇文章我们提到了可以使用反射机制破解单例模式。这篇文章我们就来谈一谈什么是反射,反射有什么用,怎么用,怎么实现反射。概述Java的反射(reflection)机制:是指在程序的运行状态中,可以构...

【Java】Redis 保存 Java 对象_redis保存数据

1. 前言这是一篇来自2018年的文章,当时已经在现在这家公司工作。公司刚起步是购买外包公司产品做定制化开发,在开发微信版的过程中遇到了一个问题。由于微信端需要通过H5的入口进行账号的绑定,同时需要在...

面试官:在 Java 中 new 一个对象的流程是怎样的?彻底被问懵了

对象怎么创建,这个太熟悉了,new一下(其实还有很多途径,比如反射、反序列化、clone等,这里拿最简单的new来讲):Dog dog = new Dog(); 我们总是习惯于固定语句的执行,却对于背...

java 二维数组开发五子棋(控制台版)人人对战

主要使用到的技术:java基础语法java面向对象思想java数组,二维数组java异常处理主要步骤和思路:1,制作一个棋盘类。棋盘类里面有行属性,列属性,二维数组属性。 有一个打印棋盘的方法。 做一...

Java Valhalla Project_阿斯顿马丁valhalla

Valhalla项目的动机和原因在本文中,我们将介绍Valhalla项目——它的历史原因、当前的开发状态,以及它发布后为日常Java开发人员带来了什么。Valhalla项目的动机和原因Oracle的J...

深圳尚学堂:干货来啦!JAVA常用代码(一)

1.获取环境变量System.getenv("PATH");System.getenv("JAVA_HOME");//2.获取系统属性System.getProperty("pencil color"...