1. 概述

在这个教程中,我们将学习在处理层次继承时实现构建者设计模式所面临的挑战。例如,电车、汽车和车辆之间的继承关系就是一个层次化的继承示例。

构建者设计模式是一种创建型设计模式,它通过方法链帮助我们逐步构建具有众多属性的复杂对象。虽然继承简化了设计,但在使用构建者模式创建对象时,它也会导致方法链实现的复杂性。

接下来,我们将借助Java泛型API提出一个高效的实现方案。

2. 问题描述

以创建VehicleCarElectricCar对象为例:

在对象层次结构的顶部是Vehicle类。Car类继承自Vehicle,然后ElectricCar类继承自Car。类与其构建器之间也存在类似的层次关系。

假设我们实例化CarBuilder类,通过方法链设置其属性,然后调用build()方法获取car对象:

CarBuilder carBuilder = new CarBuilder();
Car car = carBuilder.make("Ford")
  .model("F")
  .fuelType("Petrol")
  .colour("red")
  .build();

如果我们尝试改变方法调用的顺序

CarBuilder carBuilder = new CarBuilder();
Car car = carBuilder.make("Ford")
  .colour("red")
  .fuelType("Petrol")
  .model("F")
  .build();

colour()fuelType()方法返回的是VehicleBuilder类。因此,后续对model()方法的调用会导致编译错误,因为VehicleBuilder类中不存在这个方法。这很不方便,也是缺点。当我们试图使用ElectricVehicleBuilder类构建ElectricVehicle对象时,也会遇到类似的问题。

3. 不使用泛型的解决方案

这是一个非常直接的实现方式,子类构建器类会重写层次结构中所有基础构建器类的方法链。因此,在设置属性值的方法链中不会有编译错误。

首先,让我们看一下Vehicle类:

public class Vehicle {

    private String fuelType;
    private String colour;

    // Standard Getter methods..
    public Vehicle(VehicleBuilder builder) {
        this.colour = builder.colour;
        this.fuelType = builder.fuelType;
    }

    public static class VehicleBuilder {

        protected String fuelType;
        protected String colour;

        public VehicleBuilder fuelType(String fuelType) {
            this.fuelType = fuelType;
            return this;
        }

        public VehicleBuilder colour(String colour) {
            this.colour = colour;
            return this;
        }

        public Vehicle build() {
            return new Vehicle(this);
        }
    }
}

Vehicle类有两个属性fuelTypecolour。它还有一个内部类VehicleBuilder,其中的方法名称与Vehicle类的属性相似。这些方法返回构建器类,以便支持方法链。

现在来看Car类:

public class Car extends Vehicle {

    private String make;
    private String model;

    // Standard Getter methods..

    public Car(CarBuilder builder) {
        super(builder);
        this.make = builder.make;
        this.model = builder.model;
    }

    public static class CarBuilder extends VehicleBuilder {

        protected String make;
        protected String model;

        @Override
        public CarBuilder colour(String colour) {
            this.colour = colour;
            return this;
        }

        @Override
        public CarBuilder fuelType(String fuelType) {
            this.fuelType = fuelType;
            return this;
        }

        public CarBuilder make(String make) {
            this.make = make;
            return this;
        }

        public CarBuilder model(String model) {
            this.model = model;
            return this;
        }

        public Car build() {
            return new Car(this);
        }
    }
}

Car类继承自Vehicle,而CarBuilder类继承自VehicleBuilder。此外,CarBuilder类还需要重写colour()fuelType()方法。

现在我们可以构建一个Car对象:

@Test
void givenNoGenericImpl_whenBuild_thenReturnObject() {
    Car car = new Car.CarBuilder().colour("red")
      .fuelType("Petrol")
      .make("Ford")
      .model("F")
      .build();
    assertEquals("red", car.getColour());
    assertEquals("Ford", car.getMake());
}

在调用build()方法之前,我们可以按任意顺序设置汽车的属性。

然而,对于Car的子类,如ElectricCar,我们必须在ElectricCarBuilder类中重写CarBuilderVehicleBuilder的所有方法。所以,这不是一个非常高效的实现。

4. 使用泛型的解决方案

泛型可以帮助解决前面讨论的实施挑战。

首先,让我们修改Vehicle类中的内部构建器类:

public class Vehicle {

    private String colour;
    private String fuelType;

    public Vehicle(Builder builder) {
        this.colour = builder.colour;
        this.fuelType = builder.fuelType;
    }

    //Standard getter methods..
    public static class Builder<T extends Builder> {

        protected String colour;
        protected String fuelType;

        T self() {
            return (T) this;
        }

        public T colour(String colour) {
            this.colour = colour;
            return self();
        }

        public T fuelType(String fuelType) {
            this.fuelType = fuelType;
            return self();
        }

        public Vehicle build() {
            return new Vehicle(this);
        }
    }
}

值得注意的是,内部构建器类中的fuelType()colour()方法返回一个泛型类型。这种实现方式支持流畅风格编码或方法链,这就是众所周知的有趣地反复出现模板模式(CRTP)

现在我们来实现Car类:

public class Car extends Vehicle {

    private String make;
    private String model;

    //Standard Getters..
    public Car(Builder builder) {
        super(builder);
        this.make = builder.make;
        this.model = builder.model;
    }

    public static class Builder<T extends Builder<T>> extends Vehicle.Builder<T> {

        protected String make;
        protected String model;

        public T make(String make) {
            this.make = make;
            return self();
        }

        public T model(String model) {
            this.model = model;
            return self();
        }

        @Override
        public Car build() {
            return new Car(this);
        }
    }
}

我们在内部构建器类的签名中应用了CRTP,并使内部类的方法返回一个通用类型以支持方法链。

同样,让我们实现Car的子类ElectricCar

public class ElectricCar extends Car {
    private String batteryType;

    public String getBatteryType() {
        return batteryType;
    }

    public ElectricCar(Builder builder) {
        super(builder);
        this.batteryType = builder.batteryType;
    }

    public static class Builder<T extends Builder<T>> extends Car.Builder<T> {
        protected String batteryType;

        public T batteryType(String batteryType) {
            this.batteryType = batteryType;
            return self();
        }

        @Override
        public ElectricCar build() {
            return new ElectricCar(this);
        }
    }
}

实现几乎相同,只是内部构建器类扩展了其父类Builder<Car<T>>。对于ElectricCar的后续子类,也需要应用同样的技术。

让我们看看实际的实现情况:

@Test
void givenGenericImpl_whenBuild_thenReturnObject() {
    Car.Builder<?> carBuilder = new Car.Builder();
    Car car = carBuilder.colour("red")
      .fuelType("Petrol")
      .make("Ford")
      .model("F")
      .build();

    ElectricCar.Builder<?> ElectricCarBuilder = new ElectricCar.Builder();
    ElectricCar eCar = ElectricCarBuilder.make("Mercedes")
      .colour("White")
      .model("G")
      .fuelType("Electric")
      .batteryType("Lithium")
      .build();

    assertEquals("red", car.getColour());
    assertEquals("Ford", car.getMake());

    assertEquals("Electric", eCar.getFuelType());
    assertEquals("Lithium", eCar.getBatteryType());
}

这种方法成功地构建了CarElectricCar类型的对象。

有趣的是,我们在内部类Car.Builder<?>ElectricCar.Builder<?>的声明中使用了原始的泛型类型?。这是因为我们需要确保像carBuilder.colour()carBuilder.fuelType()这样的方法调用返回的是Car.Builder,而不是其父类Vehicle.Builder

同样,ElectricCarBuilder.make()ElectricCarBuilder.model()方法的调用应该返回ElectricCarBuilder,而不是CarBuilder类。没有这个方法,链式调用将无法进行。

5. 总结

在这篇文章中,我们讨论了在处理继承时构建者设计模式的挑战。Java泛型和有趣地反复出现模板模式帮助我们找到了解决方案。这样,我们就可以在不担心方法调用顺序的情况下,使用方法链设置构建器类中的属性值。

如往常一样,使用的代码可以在GitHub上找到