使用Java泛型实现构建器Builder模式与继承

在本教程中,我们将了解在处理层次继承时实现构建器设计模式的挑战。分层继承的一个示例可以是电动汽车、汽车和车辆之间的继承。

构建器模式是一种创造性的设计模式,它有助于在方法链的帮助下,在逐步过程中简化构建具有许多属性的复杂对象。虽然继承有助于简化设计,但它也会导致实现方法链接以在构建器模式中创建对象的复杂性。

此外,我们将在Java Generics API的帮助下提出一个有效的实现。

让我们举一个应用 Builder 模式创建Vehicle、Car和ElectricCar类型的对象的示例:
在对象层次结构的顶部,有Vehicle类。Car类扩展了Vehicle,然后ElectricCar扩展了Car。与这些对象类似,它们的构建者之间也存在层次关系。

让我们实例化CarBuilder类,通过方法链设置其属性,最后调用build()方法来获取汽车对象:

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

color()和fuelType()方法返回VehicleBuilder类。因此,对model() 的 后续调用将导致编译错误,因为它在VehicleBuilder类中不存在。这是不方便的并且是一个缺点。当我们尝试使用ElectricVehicleBuilder类构建ElectricVehicle对象时,会看到类似的行为。

1、没有泛型的解决方案
这是一个非常简单的实现,其中子构建器类覆盖层次结构中所有基本构建器类的链接方法。因此,在方法链接设置属性值的过程中不会出现编译错误。

让我们首先看一下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类有两个属性FuelType和color。它还具有一个内部类 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类必须重写color()和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中CarBuilder和VehicleBuilder的所有方法。因此,这不是一个非常有效的实现。

2、泛型解决方案
泛型可以帮助克服前面讨论的实现中面临的挑战。

为此,我们修改Vehicle类中的内部Builder类:

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

值得注意的是,内部Builder类中的FuelType()和color()方法返回泛型类型。这种实现有利于流畅的风格编码或方法链接。这是一种众所周知的设计模式,名称为“好奇重复模板模式”(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);
        }
    }
}

我们在内部Builder类的签名中应用了 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>的内部Builder类之外,实现几乎保持不变。同样的技术必须应用于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());
}

该方法成功构建了Car和ElectricCar类型的对象。

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

同样,调用ElectricCarBuilder.make()和ElectricCarBuilder.model()的方法应返回ElectricCarBuilder而不是CarBuilder类。如果没有这个方法,链接将是不可能的。

结论
在本文中,我们讨论了构建器设计模式在处理继承时面临的挑战。Java 泛型和奇怪的重复模板模式帮助我们实现了一个解决方案。这样,我们就可以使用方法链来设置构建器类中的属性值,而不必担心方法调用的顺序。