创建型设计模式之建造者模式

概述

设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。

大部分设计模式要解决的都是代码的可扩展性问题。

对于灵活多变的业务,需要用到设计模式,提升扩展性和可维护性,让代码能适应更多的变化;

设计模式的核心就是,封装变化,隔离可变性


设计模式解决的问题:

  • 创建型设计模式主要解决"对象的创建"问题,创建和使用代码解耦;
  • 结构型设计模式主要解决"类或对象的组合或组装"问题,将不同功能代码解耦;
  • 行为型设计模式主要解决的就是"类或对象之间的交互"问题。将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。

创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。

  • 单例模式用来创建全局唯一的对象。
  • 工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
  • 建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,"定制化"地创建不同的对象。
  • 原型模式针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的。

设计模式关注重点: 了解它们都能解决哪些问题,掌握典型的应用场景,并且懂得不过度应用。

经典的设计模式有 23 种。随着编程语言的演进,一些设计模式(比如 Singleton)也随之过时,甚至成了反模式,一些则被内置在编程语言中(比如 Iterator),另外还有一些新的模式诞生(比如 Monostate)。

常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。

不常用的有:原型模式。

建造者模式(Builder 模式)

定义:

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

  • 按需要设置不同的可选参数,还可以检验参数,构建一个不可变对象
  • 建造者模式,先设置建造者的变量,然后再一次性地创建对象,让对象一直处于有效状态。
java 复制代码
public class ResourcePoolConfig {
    private String name;
    private int maxTotal;
    private int maxIdle;
    private int minIdle;

    private ResourcePoolConfig(Builder builder) {
        this.name = builder.name;
        this.maxTotal = builder.maxTotal;
        this.maxIdle = builder.maxIdle;
        this.minIdle = builder.minIdle;
    }
    //...省略getter方法...

    //我们将Builder类设计成了ResourcePoolConfig的内部类。
    //我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
    public static class Builder {
        private static final int DEFAULT_MAX_TOTAL = 8;
        private static final int DEFAULT_MAX_IDLE = 8;
        private static final int DEFAULT_MIN_IDLE = 0;

        private String name;
        private int maxTotal = DEFAULT_MAX_TOTAL;
        private int maxIdle = DEFAULT_MAX_IDLE;
        private int minIdle = DEFAULT_MIN_IDLE;

        public ResourcePoolConfig build() {
            // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
            if (StringUtils.isBlank(name)) {
                throw new IllegalArgumentException("...");
            }
            if (maxIdle > maxTotal) {
                throw new IllegalArgumentException("...");
            }
            if (minIdle > maxTotal || minIdle > maxIdle) {
                throw new IllegalArgumentException("...");
            }

            return new ResourcePoolConfig(this);
        }

        public Builder setName(String name) {
            if (StringUtils.isBlank(name)) {
                throw new IllegalArgumentException("...");
            }
            this.name = name;
            return this;
        }

        public Builder setMaxTotal(int maxTotal) {
            if (maxTotal <= 0) {
                throw new IllegalArgumentException("...");
            }
            this.maxTotal = maxTotal;
            return this;
        }
       ...
      
    }
}

// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
    .setName("dbconnectionpool")
    .setMaxTotal(16)
    .setMaxIdle(10)
    .setMinIdle(12)
    .build();

工厂模式对比:

创建方式 应用场景
new 创建逻辑简单,没有影响实例生成的参数或条件
工厂模式 根据参数或条件生成不同实例,同一类实例,对象不多
Builder模式 创建实例时可以设置不同的参数组合,每种组合可以表现不同的行为,实例对象很多
  1. 工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
  2. 建造者模式是用来创建一种类型的复杂对象,可以通过设置不同的可选参数,"定制化"地创建不同的对象。

使用场景:

如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合 set() 方法来解决。但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。

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

在编码期避免创建错误

定义合法参数

java 复制代码
public enum Color {
    RED, BLACK
}

public enum Model {
    NORMAL, MAX
}

public enum CameraNum {
    TWO, THREE
}

public enum MemorySize {
    G128, G256, G512
}

新增适用红色IPhone的参数定义

用于红色IPhone,限定内存范围,避免对象属性创建错误

java 复制代码
// 用于红色IPhone,限定内存范围
public enum RedMemorySize {
    G256 {
        @Override public MemorySize getMemorySize() {
            return MemorySize.G256;
        }
    },
    G512 {
        @Override public MemorySize getMemorySize() {
            return MemorySize.G512;
        }
    };

    public abstract MemorySize getMemorySize();
}

定义不同的Builder创建不同的Iphone

java 复制代码
public class IPhone {

    // 成员定义不变
    private Color color;
    private Model model;
    private CameraNum cameraNum;
    private MemorySize memorySize;

    // 构造器不变
    public IPhone(Color color, Model model, CameraNum cameraNum, MemorySize memorySize) {
        this.color = color;
        this.model = model;
        this.cameraNum = cameraNum;
        this.memorySize = memorySize;
    }

    // 黑色IPhone Builder
    public static final class BlackBuilder {
        private MemorySize memorySize;

        // 其他参数没得选,只需保留此方法
        public BlackBuilder setMemorySize(MemorySize memorySize) {
            this.memorySize = memorySize;
            return this;
        }

        public IPhone build() {
            // 限定的参数直接传入
            IPhone iPhone = new IPhone(Color.BLACK, Model.NORMAL, CameraNum.TWO, this.memorySize);
            return iPhone;
        }
    }

    // 红色IPhone Builder
    public static final class RedBuilder {
        private Model model;
        private CameraNum cameraNum;
        private MemorySize memorySize;

        public RedBuilder setModel(Model model) {
            this.model = model;
            return this;
        }

        public RedBuilder setCameraNum(CameraNum cameraNum) {
            this.cameraNum = cameraNum;
            return this;
        }

        // 注意这里入参是RedMemorySize,用于限定内存范围只能是256G和512G
        public RedBuilder setMemorySize(RedMemorySize memorySize) {
            this.memorySize = memorySize.getMemorySize();
            return this;
        }

        public IPhone build() {
            // 限定的参数直接传入
            IPhone iPhone = new IPhone(Color.RED, this.model, this.cameraNum, this.memorySize);
            return iPhone;
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("color=").append(color.toString()).append(", ");
        builder.append("model=").append(model.toString()).append(", ");
        builder.append("cameraNum=").append(cameraNum.toString()).append(", ");
        builder.append("memorySize=").append(memorySize.toString());
        return builder.toString();
    }
}

测试代码

java 复制代码
public class BuilderDemo {
    public static void main(String[] args) {
        // 红色专有Builder,在开发时即可避免创建错误对象
        IPhone.RedBuilder redBuilder = new IPhone.RedBuilder();
        IPhone redPhone = redBuilder.setCameraNum(CameraNum.THREE)
                .setMemorySize(RedMemorySize.G512)
                .setModel(Model.MAX).build();

        // 黑色专有Builder,在开发时即可避免创建错误对象
        IPhone.BlackBuilder blackBuilder = new IPhone.BlackBuilder();
        IPhone blackPhone = blackBuilder.setMemorySize(MemorySize.G128).build();

        System.out.println(redPhone);
        System.out.println(blackPhone);
    }
}

输出:

java 复制代码
color=RED, model=MAX, cameraNum=THREE, memorySize=G512
color=BLACK, model=NORMAL, cameraNum=TWO, memorySize=G128

可以看到,这时创建的黑色IPhone是正确的。

创建者模式经典范式

  • 创建者和要创建对象分离,这样可解耦,职责更清晰。
  • 创建者可作为创建对象的内部类(通常是静态内部类),这样更内聚,使得很容易找到创建者。
  • 由于不同的条件/参数组合创建出的对象实例有不同行为,错误组合可能导致错误结果时,可以通过不同的Builder来创建对象,在编码时就避免犯错。
  • 通过链式创建方式使得代码更简洁。

小结

  • 每个设计模式总有不能被替代的用途,如果发现能互相替代时,就用最简单的那种。

  • 尽早避免编码犯错能使代码更健壮,纠错成本更低,所以应尽可能的在前期避免错误发生。

  • 工厂模式和Builder模式的选择在于是否有很多不同参数组合会影响被创建实例的行为,如果有就使用Builder模式,否则会因为要穷举各种参数组合导致工厂方法膨胀。

相关推荐
工程师老罗2 小时前
Java笔试面试题AI答之设计模式(4)
java·开发语言·设计模式
18你磊哥3 小时前
java重点学习-设计模式
java·学习·设计模式
蔚一7 小时前
Java设计模式(单例模式)——单例模式存在的问题(完整详解,附有代码+案例)
java·开发语言·单例模式·设计模式
严文文-Chris8 小时前
【设计模式-备忘录】
java·设计模式
岁岁岁平安12 小时前
《飞机大战游戏》实训项目(Java GUI实现)(设计模式)(简易)
后端·游戏·设计模式·飞机大战·java-gui
丶白泽14 小时前
重修设计模式-结构型-适配器模式
前端·设计模式·适配器模式
CV猿码人14 小时前
设计模式-适配器模式
java·设计模式·适配器模式
极地星光14 小时前
设计模式-适配器模式
c++·设计模式·适配器模式
java_heartLake19 小时前
设计模式之代理模式
java·设计模式·代理模式
被拯救的威尼斯1 天前
设计模式-结构型-11-代理模式
设计模式·代理模式