【设计模式】建造者模式

773 阅读7分钟

建造者模式主要针对复杂对象的创建,是一种比较复杂的对象创建型模式。本文主要介绍:建造者模式带Director实现,无Director实现,Lombok实现、

模式背景

我们为什么要有建造者模式呢?在我们项目中可能存在一些比较复杂的对象的创建,比如一个对象里面组合了大量的其他的对象,如果将创建这些对象的代码放在使用方来编写。那么所有使用到该对象的地方都要编写巨冗长的代码,这显然不够优雅!本着公共的重复的代码就应该抽取在同一个地方的原则,我们应该将创建这个对象的职责抽取出来,交给一个专门的类来做!所以创建者模式就是为此而生的。

我们使用构建者模式可以将创建的逻辑放在一个对应的Builder类里面。比如我们现在前端需要一个返回的VO对象。这个VO对象需要根据浏览器还是移动端等情况,返回不同的结构类型,但是整体大概都是相同的一个大的VO对象。这时候我们就可以创建2个Builder:一个针对电脑的Builder,一个针对移动端的Builder。这样我们只需要编写好对应的Builder以后使用的时候就可以直接复用。

UML

原理

构建者模式,即将创建一个复杂对象的逻辑抽取到Builder类中进行,将一个复杂的对象的构建和他的表示分离。

又因为这个对象可能需要多个表示,所以我们需要对这些Builder进行抽象,使得可以扩展Builder实现类来完成不同的表示。

又因为不同Builder的实现类,只是负责复杂对象各个部分的具体实现,我们可能需要对复杂对象进行装配的时候的次序有要求:比如先装Part2再装Part1,又或者说我们某些情况不需要装配Part2了,所以这时候需要提供一个Director类来配置装配秩序。

客户端只需要和Director进行交互。

当然我个人遇到的大部分情况,都可以将Director去掉,构建的过程都放在Builder实现类中即可。

但是这个是针对构建次序要求不多的情况的(很多时候我们只需要装配好就行,并没有次序要求),如果装配次序要求的多,那么每次新的次序要求的时候,我们就要修改Builder类,这样就违背了开闭原则。

建造者模式和抽象工厂模式有点相似:都是按照一个模板来构建表示的对象。区别是建造者模式是构建一个大的复杂对象整体作为返回,抽象工厂则是返回一系列的零件对象。(返回一个车和返回车胎,车灯等的区别)

必要条件

  1. 需要被构建的实体类(一般是复杂的实体对象)。
  2. 抽象Builder
    • 接口或者抽象类;
    • 让各种Builder统一约束。申明buildPartX方法以及getResult方法。
  3. Builder实现类。
    • 为各个Part提供具体的实现逻辑。没一个Builder实现类都应当对应着一个业务需求Bean。
  4. 一个构建器
    • 目的1:让build的过程和客户端进行隔离。
    • 目的2:真正的控制复杂对象的构建逻辑,Builder只是把方法都确定好了,Director负责调用来装配。

实现

实体类(我们这里以一个汽车为例子)

public class Car {
    private String light;
    private String wheel;
    private String chair;
}

构建抽象类

public abstract class CarBuilder {
    /**
    我们将Car对象在这里创建,实现类继承就好了。
    为什么Car对象在Builder中创建,我想是为了和Director进行隔离吧。因为从对象->Builder->Director中间有层次关系,尽量将每一层的隔离做好。
     */
    Car car = new Car();
    public abstract void buildLight();
    public abstract void buildWheel();
    public abstract void buildChair();
    /**这个可以是一个静态方法*/
    public Car createCar() {
        return car;
    }
}

具体Builder实现

public class BigCarBuilder extends CarBuilder {
    @Override
    public void buildLight() {
        this.car.setLight("big light");
    }
    @Override
    public void buildWheel() {
        this.car.setWheel("big wheel");
    }
    @Override
    public void buildChair() {
        this.car.setChair("big chair");
    }
}

指挥类

public class Director {
   /** 构造者模式第二个关键点:
     * 指挥类里面内持一个builder,让客户端只在乎使用哪个builder来构建就行。
     * 通过这样客户端就完全只需要知道自己用哪个builder就行了,然后初始化好builder直接往指挥类里面塞。 */
    public CarBuilder builder;
    public Director(CarBuilder builder) {
        this.builder = builder;
    }
    public CarBuilder getBuilder() {
        return builder;
    }
    /**构建对象*/
    public Car build() {
        builder.buildLight();
        builder.buildWheel();
        builder.buildChair();
        return builder.createCar();
    }
}

使用

CarBuilder builder = new BigCarBuilder();
//具体使用哪个builder来构造对象,可以使用配置文件来配置,增加灵活度。
Director director = new Director(builder);
Car car = director.build();
System.out.println(car);

优缺点

  • 优点
    • 职责拆分细,扩展性相对就高些。
  • 缺点
    • 结构复杂,代码量大,你是不是也觉得为了创建一个对象都一下子就延伸了2个步骤很繁琐。

就是上面全部必要条件的实现。如果构建一个对象很复杂,还是推荐使用该方式进行结构设计。

无Director实现

之前说过,在某些情况下,我们可以将Director类和Builder进行融合,将Director的职责交给Builder,构建的过程让Builder自己也同时完成。

实现

abstract class CarBuilder2 {
    protected Car car = new Car();
    public abstract void buildLight();
    public abstract void buildWheel();
    public abstract void buildChair();
  	/*builder自己来构建*/
    public Car build() {
        this.buildWheel();
        this.buildChair();
        this.buildLight();
        return car;
    }
}
public class NoDirectorCarBuilder extends CarBuilder2 {
    @Override
    public void buildLight() {
        this.car.setLight("no director light");
    }
    @Override
    public void buildWheel() {
        this.car.setWheel("no director wheel");
    }
    @Override
    public void buildChair() {
        this.car.setChair("no director chair");
    }
    /*验证*/
    public static void main(String[] args) {
        NoDirectorCarBuilder builder = new NoDirectorCarBuilder();
        Car car = builder.build();
        System.out.println(car);
    }
}

这么做简化了 Director类,但是也将职责全交给了Builder,这也必然会加重了Builder的职责。

但是当一个对象要求十分复杂的时候,还是推荐使用Director的方式来进行构建。 扩展性更高,也更加符合单一职责原则。

Lombok实现

Java开发中经常使用的插件:lombok 中有一个@Builder注解用来很方便,他也是利用了构建者模式。使用这个注解来创建对象还是很方便的。其内部实现方式如下:

class Person {
    private String name;
    private String age;
    /*lombok开始*/

    /**
     * 关键4 构建对象
     */
    public static PersonBuilder bulder() {
        return new PersonBuilder();
    }

    /**
     * 关键1 构造
     */
    public Person(PersonBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    /**
     * 关键2 构建类
     */
    public static class PersonBuilder {
        /**
         * 关键3 属性copy
         */
        private String name;
        private String age;

        public PersonBuilder name(String name) {
            this.name = name;
            return this;
        }

        public PersonBuilder age(String age) {
            this.age = age;
            return this;
        }

        public Person build() {
            return new Person(this);
        }
    }
    
    public static void main(String[] args) {
        Person p = Person.bulder().name("justin").age("24").build();
        System.out.println(p.getName());
    }
}

其内部会为当前类创建一个Builder类,该类拥有和原始类同样的属性,我们通过builder来对属性就行赋值。最后通过builder的对象来构造原始类的对象。

优缺点

整体来看,构建者模式的优缺点如下:

优点

  • 对象的创建和表示分离,符合解耦的思想。
  • 每个构建者相互独立,可以动态的添加或者替换构建者来创建不同的表示,扩展性高。
  • 一定程度上,可以让我们更清晰的了解复杂对象的创建逻辑(只需要盯准Director类就行了)。

缺点

  • 适用性是那种大致相同,只是各个组成部分都有自己不同的表示的。如果差异太大不适合该模式。
  • 如果对复杂对象需求变化太多,会增加很多构建类,增加了系统复杂度。

使用场景

  1. 构建复杂的对象,一个对象内部包含了很多其他的对象。
  2. 复杂对象对外的表示需要能扩展,很有可能产品会对这个复杂对象的表示提出新需求。
  3. 生成的复杂对象,对象创建过程中,内部的对象有相互依赖的关系,使用Director可以很好的控制和让我们理解这些顺序。

相关代码:github.com/zhaohaoren/…

如有代码和文章问题,还请指正!感谢!