在本教程中,我们将了解在处理层次继承时实现构建器设计模式的挑战。分层继承的一个示例可以是电动汽车、汽车和车辆之间的继承。
构建器模式是一种创造性的设计模式,它有助于在方法链的帮助下,在逐步过程中简化构建具有许多属性的复杂对象。虽然继承有助于简化设计,但它也会导致实现方法链接以在构建器模式中创建对象的复杂性。
此外,我们将在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 泛型和奇怪的重复模板模式帮助我们实现了一个解决方案。这样,我们就可以使用方法链来设置构建器类中的属性值,而不必担心方法调用的顺序。