建造者模式

237 阅读4分钟

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

建造者模式

是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。

优点

  1. 封装性好,构建和表示分离。
  2. 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
  3. 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。

缺点

  1. 产品的组成部分必须相同,这限制了其使用范围。
  2. 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。

使用场景

当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。

结构

  1. 产品角色(Product):最终生成的对象;它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
  2. 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
  3. 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  4. 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

image.png

传统演示

1、产品类

public class Jacket {
    private Integer sleeve;
    private Integer buttons;
    private Integer pocket;
}

2、Builder类

public abstract class Builder {
    abstract void buildSleeve();
    abstract void buildButtons();
    abstract void buildPocket();
    abstract Jacket createJacket();
} 

3、Concrete Builder类

建造具体的产品, 指定具体产品各部件需要的值
public class LiningJacketBuilder extends Builder{
    private Jacket jacket = new Jacket();
    @Override
    void buildSleeve(int sleeve) {
        this.jacket.setSleeve(sleeve);
    }
    @Override
    void buildButtons(int buttons) {
        this.jacket.setButtons(buttons);
    }
    @Override
    void buildPocket(int pocket) {
        this.jacket.setPocket(pocket);
    }
    @Override
    Jacket createJacket() {
        return this.jacket;
    }
}

4、Director

public class Director {
    private Builder builder;
    public Director(Builder builder) {
        this.builder = builder;
    }
    public Jacket construct(int sleeve, int buttons, int pocket) {
        this.builder.buildSleeve(sleeve);
        this.builder.buildButtons(buttons);
        this.builder.buildPocket(pocket);
        return this.builder.createJacket();
    }
}

5、客户端

public class Click {
    public static void main(String[] args) {
        Director director = new Director(new LiningJacketBuilder());
        Jacket jacket = director.construct(2,5,1);
        System.out.println(jacket.toString());
        // Jacket [sleeve=2, buttons=5, pocket=1]
    }
}

总结

传统的建造者模式跟工厂模式有些类型,都是返回一个具体产品。

  • 区别1:Director类似将产品各个部件组装成一个产品,而工厂直接生产一个产品
  • 区别2:工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象;而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象 。
  • 区别3:工厂模式最终是构建一个对象,而建造者模式的目的是构建一个复杂不可变对象

简化演示

  1. 在Jacket 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
  2. 在Jacket中创建一个private的构造函数,参数为Builder类型
  3. 在Builder中创建一个public构造函数
  4. 在Builder中创建设置函数,对Jacket中那些可选参数进行赋值,返回值为Builder类型的实例
  5. 在Builder中创建一个build()方法,在其中构建Jacket的实例并返回
public class Jacket {
    private Integer sleeve;
    private Integer buttons;
    private Integer pocket;
    private Jacket(Builder builder) {
        this.sleeve = builder.sleeve;
        this.buttons = builder.buttons;
        this.pocket = builder.pocket;
    }
    public static class Builder{
        private Integer sleeve;
        private Integer buttons;
        private Integer pocket;
        public Builder() {}
        public Builder setSleeve(int sleeve) {
            this.sleeve = sleeve;
            return this;
        }
        public Builder setButtons(int buttons) {
            this.buttons = buttons;
            return this;
        }
        public Builder setPocket(int pocket) {
            this.pocket = pocket;
            return this;
        }        
        public Jacket build(){
            return new Jacket(this);
        }
    }
    @Override
    public String toString() {
        return "Jacket [sleeve=" + sleeve + ", buttons=" + buttons + ", pocket=" + pocket + "]";
    }
}

在客户端使用链式调用,一步一步的把对象构建出来。

public class Click {
    public static void main(String[] args) {
        Jacket jacket = new Jacket.Builder().setButtons(5).setPocket(1).setSleeve(2).build();
        System.out.println(jacket.toString());
        // Jacket [sleeve=2, buttons=5, pocket=1]
    }
}

总结

使用传统建造者模式,如果参数很多,代码可读性会非常差,而且容易引入错误。而重构简化后,上面的示例代码只是传入三个参数,如果参数是十几个甚至更多,builder 模式的优势将会更加明显,传递参数更加灵活,代码具有更高的可读性.

文章纯属个人见解,有不同见解可以在评论区一起讨论(*^▽^*)