美文网首页
设计模式(4) : 建造者模式

设计模式(4) : 建造者模式

作者: a_salt_fish | 来源:发表于2019-01-20 18:25 被阅读0次

    定义:

    将一个复杂对象的购进啊与它的表示进行分离, 使得同样的构建过程可以创建不同的表示

    用户只需要指定需要的类型和组件, 建造过程及细节无需知道.

    类型:

    创建型

    使用场景

    • 对象有非常复杂的内部结构(属性很多,或创建过程很复杂)

    • 需要将对象的创建与使用分离

    coding

    业务场景 : 组装计算机. 计算机由主板,CPU, 硬盘, 键盘, 显示器, 鼠标键盘等组件组装而成,这些组件

    通常来说我们是这样做的, 定义一个Computer

    public class Computer1 {
        private String cpu;
        private String mainBoard;
        private String hardDisk;
        private String memory;
        // get and set ...
        @Override
        public String toString() {
            return "Computer2{" +
                    "cpu='" + cpu + '\'' +
                    ", mainBoard='" + mainBoard + '\'' +
                    ", hardDisk='" + hardDisk + '\'' +
                    ", memory='" + memory + '\'' +
                    '}';
        }
    }
    

    创建Computer

    public class Test1 {
        public static void main(String[] args) {
            // 缺少组件抛出异常
            Computer1 computer = new Computer1();
            computer.setCpu("i5");
            computer.setHardDisk("500G");
            computer.setHardDisk("宏基");
            computer.setHardDisk("16G");
            System.out.println(computer);
        }
    }
    

    正常输出, 成功得到了一个电脑, 但是这样的方式有明显的缺点, 首先一连串的set方法让人看起来很头大, 另一个隐含的隐患就是我们只能依赖程序员的编码素质来保证得到的对象的质量, 如果程序员在一连串的set之后头晕转向忘记了一个属性 setCpu, 那我们将会得到一个无法工作的电脑.

    接下来用建造者模式来改造一下
    Compute的属性保持不变, 将它的构造器设置为私有,增加了一个内部类Builder,并提供了一个静态方法来获取Builder.

    public class Computer2 {
    
        private String cpu;
        private String mainBoard;
        private String hardDisk;
        private String memory;
    
        private Computer2(){
        }
    
        public static Builder builder(){
            return new Builder();
        }
    
        // get and set ...
    
        @Override
        public String toString() {
            return "Computer2{" +
                    "cpu='" + cpu + '\'' +
                    ", mainBoard='" + mainBoard + '\'' +
                    ", hardDisk='" + hardDisk + '\'' +
                    ", memory='" + memory + '\'' +
                    '}';
        }
    
        public static class Builder {
            private Computer2 computer2;
            public Builder() {
                computer2 = new Computer2();
            }
            public Builder buildCPU(String cpu){
                computer2.cpu = cpu;
                return this;
            }
            public Builder buildMainBoard(String mainBoard){
                computer2.mainBoard = mainBoard;
                return this;
            }
            public Builder buildHardDisk(String hardDisk){
                computer2.hardDisk = hardDisk;
                return this;
            }
            public Builder buildMemory(String memory){
                computer2.memory = memory;
                return this;
            }
            public Computer2 build(){
                if(computer2.cpu == null){
                    throw new RuntimeException("缺少CPU");
                }
                // 其他校验 ...
                return computer2;
            }
    
        }
    }
    
    • 建造器的build...方法都会返回建造器本身
    • 一连串的build...之后必须以一个build方法终结,来获取对象. 在build方法中可以对对象的完整性做校验,来保证对象可以按照预期的方式运行.
    public class Test2 {
        public static void main(String[] args) {
            // 缺少组件抛出异常
            // Computer2 computer = Computer2.builder().build();
            Computer2 computer = Computer2
                    .builder()
                    .buildCPU("i7")
                    .buildHardDisk("1T")
                    .buildMainBoard("华硕")
                    .buildMemory("32G")
                    .build();
            System.out.println(computer);
        }
    }
    

    源码中的应用

    JDK中最常用的StringBuilder就是一个标准的建造者模式的应用, 它提供了一系列的append方法的重载, 可以方便高效的使用StringBuilder来连接字符串,字符,数字或对象

    StringBuilder继承自抽象类, StringBuffer也继承了它,与StringBuilder不同的是StringBuffer是一个线程安全的类, 它为很多方法做了同步处理, 所以效率要低于StringBuilder

    实现原理 : 在 AbstractStringBuilder 维护了一个字符数组,和字符数量

        /**
         * 保存字符串的字符数组
         */
        char[] value;
        /**
         * 数组中的有效长度
         */
        int count;
    

    在调用 append(*)方法时, StringBuilder直接调用的父类AbstractStringBuilder 中的实现, AbstractStringBuilder 中为不同的数据类型做了很多优化处理,

          public AbstractStringBuilder append(boolean b) {
            if (b) {
                ensureCapacityInternal(count + 4);
                value[count++] = 't';
                value[count++] = 'r';
                value[count++] = 'u';
                value[count++] = 'e';
            } else {
                ensureCapacityInternal(count + 5);
                value[count++] = 'f';
                value[count++] = 'a';
                value[count++] = 'l';
                value[count++] = 's';
                value[count++] = 'e';
            }
            return this;
        }
        public AbstractStringBuilder append(char c) {
            ensureCapacityInternal(count + 1);
            value[count++] = c;
            return this;
        }
    

    ensureCapacityInternal 方法:
    每次调用append 增加新的字符时, 有效字符count的长度都会发生变化, 有可能会超过数组value的长度 (默认初始长度为16位), 这时候AbstractStringBuilder 提供了一个AbstractStringBuilder 来动态扩容数组,如果发现有效长度 count 加上 新增的字符数量 appendLength 小于 value的长度, 在ensureCapacityInternal 中就会调用 newCapacity方法来获取一个新的数组长度(规则为count * 2 + 2,比如原来是16扩容后就变成34)并根据新计算出来的长度新创建一个数组,并将旧数据copy到新数组,然后再将要append的数据添加到新数组中.

        private void ensureCapacityInternal(int minimumCapacity) {
            // overflow-conscious code
            if (minimumCapacity - value.length > 0) {
                value = Arrays.copyOf(value,
                        newCapacity(minimumCapacity));
            }
        }
        private int newCapacity(int minCapacity) {
            // overflow-conscious code
            int newCapacity = (value.length << 1) + 2;
            if (newCapacity - minCapacity < 0) {
                newCapacity = minCapacity;
            }
            return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
                ? hugeCapacity(minCapacity)
                : newCapacity;
        }
    

    最后调用toString返回一个新的字符串

        public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }
    

    优点:

    • 封装性好, 隔离了创建细节
    • 扩展性好, 建造类相互独立, 降低耦合性

    缺点:

    • 产生了多余的Builder对象
    • 产品发生变化时需要修改建造者对象,增加了维护成本

    github源码

    相关文章

      网友评论

          本文标题:设计模式(4) : 建造者模式

          本文链接:https://www.haomeiwen.com/subject/dznxjqtx.html