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

概述

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

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

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

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

设计模式解决的问题:

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

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

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

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

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

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

不常用的有:原型模式。

建造者模式(Builder 模式)

定义:

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

  • 按需要设置不同的可选参数,还可以检验参数,构建一个不可变对象
  • 建造者模式,先设置建造者的变量,然后再一次性地创建对象,让对象一直处于有效状态。
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() 方法来设置属性值的方式就不适用了。

在编码期避免创建错误

定义合法参数

public enum Color {
    RED, BLACK
}

public enum Model {
    NORMAL, MAX
}

public enum CameraNum {
    TWO, THREE
}

public enum MemorySize {
    G128, G256, G512
}

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

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

// 用于红色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

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

测试代码

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

输出:

color=RED, model=MAX, cameraNum=THREE, memorySize=G512
color=BLACK, model=NORMAL, cameraNum=TWO, memorySize=G128

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

创建者模式经典范式

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

小结

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

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

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

全部评论

相关推荐

昨天 12:43
已编辑
小码王_运维
蚂蚁岗位内推官:1 你觉得你有哪些缺点和优点? 2 你怎么评价你面试的这家公司? 3 你在校期间,有没有哪段时间或者某件事情让你受挫? 4 在校期间遇到最有挑战的事情是什么? 5 目前手上有 offer 吗? 6 自我介绍 7 职业规划 8 报学校专业是怎么考虑的? 9 工作城市 10 你是独生子女吗? 11 那你男朋友吗? 12 那你们出来面试都了解过哪些企业? 13 到后期你们每个人手上有好几个offer,哪些因素决定你们选择这家公司? 14 你更倾向哪种公司?有什么特别的点? 15 你大学有没有特别难忘的经历或者项目分享一下的? 16 团队合作中遇到什么问题? 17 对互联网加班有什么看法? 18 那你现在的技术薄弱点在哪里,怎么去突破? 19 你的兴趣爱好有哪些? 20 现在进度最快的公司是哪家? 21 拿到哪几家offer,是否谈过薪资等
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务