1. 概述
在这个教程中,我们将学习在处理层次继承时实现构建者设计模式所面临的挑战。例如,电车、汽车和车辆之间的继承关系就是一个层次化的继承示例。
构建者设计模式是一种创建型设计模式,它通过方法链帮助我们逐步构建具有众多属性的复杂对象。虽然继承简化了设计,但在使用构建者模式创建对象时,它也会导致方法链实现的复杂性。
接下来,我们将借助Java泛型API提出一个高效的实现方案。
2. 问题描述
以创建Vehicle
、Car
和ElectricCar
对象为例:
在对象层次结构的顶部是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
类有两个属性fuelType
和colour
。它还有一个内部类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
类中重写CarBuilder
和VehicleBuilder
的所有方法。所以,这不是一个非常高效的实现。
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());
}
这种方法成功地构建了Car
和ElectricCar
类型的对象。
有趣的是,我们在内部类Car.Builder<?>
和ElectricCar.Builder<?>
的声明中使用了原始的泛型类型?
。这是因为我们需要确保像carBuilder.colour()
和carBuilder.fuelType()
这样的方法调用返回的是Car.Builder
,而不是其父类Vehicle.Builder
。
同样,ElectricCarBuilder.make()
和ElectricCarBuilder.model()
方法的调用应该返回ElectricCarBuilder
,而不是CarBuilder
类。没有这个方法,链式调用将无法进行。
5. 总结
在这篇文章中,我们讨论了在处理继承时构建者设计模式的挑战。Java泛型和有趣地反复出现模板模式帮助我们找到了解决方案。这样,我们就可以在不担心方法调用顺序的情况下,使用方法链设置构建器类中的属性值。
如往常一样,使用的代码可以在GitHub上找到。