使用设计模式实现实体中多个依赖属性的验证模块

22-12-20 banq

有没有想过如何对属于具有多个依赖属性的实体的 API 请求/响应执行验证?在这种情况下可以执行什么类型的验证?使用什么设计模式?如何构建代码?底层设计应该是什么样的?如何使代码和设计具有可扩展性以适应验证模块的更多依赖属性?如果与属性相关的任何逻辑在未来得到增强,如何使代码和设计灵活以确保需要最少的更改?

如果您正在寻找上述问题的答案,那么本文可能有助于您从头开始为您的代码存储库设计和编写验证模块。
让我们首先举一个复杂实体的例子:

实体

Vehicle {
  private String id;
  private VehicleType vehicleType; 

  // Dependent on attribute vehicleType 
  private FuelType fuelType;

  // Dependent on attribute vehicleType
  private Set<DriveMode> driveModesSupported;

  // Dependent on attribute fuelType
  private VehicleData vehicleData;

  private String model;
  private manufacturingYear;
  ...
}

VehicleType {
  enum {TWO_WHEELER, FOUR_WHEELER}
}

FuelType {
  enum {PETROL, DIESEL, CNG, ELECTRIC}
}

DriveMode {
  enum {ECONOMY, SPORTS, CRUISE, UPHILL}
}

VehicleData {
  "engineLife": <years>,
  "rechargeable": <boolean>,
  "recyclable": <boolean>,
  "biofriendly": <boolean>,
  ...
}


1.属性vehicleType是独立的,是创建和验证链背后的管理因素。

2.属性fuelType依赖于属性vehicleType,因为少数车辆不支持某些燃料类型;例如:CNG燃料类型目前只支持汽车和公共汽车,不支持两轮车。

3.属性driveModesSupported也是类似的情况;driveModesSupported依赖于属性vehicleType。一些驱动模式只被某些类别的车辆所支持;例如:巡航和运动模式只被某些四轮驱动(如轿车和卡车)所支持,而不被两轮驱动所支持。

4.属性vehicleData依赖于属性fuelType,因为车辆的属性,如发动机寿命、可充电性、可回收性、生物友好性等,取决于车辆运行的燃料。例如:CNG车辆是生物友好型的,ELIC车辆是生物友好型的,也是可充电的,汽油和柴油车辆既不是生物友好型的也不是可充电的。


使用的设计模式
这个问题可以通过串联使用 3 种设计模式来解决:

  1. 责任链 
  2. 工厂模式 
  3. 抽象工厂模式 

为什么责任链?
由于实体的属性之间存在多重依赖关系(一个属性的值取决于类型和其他属性的值),责任链设计模式非常适合这种场景。

为什么是工厂模式?
由于有多种接口实现,例如:1. 车辆级别的几个实现:双轮车和四轮车。2. 很少有四轮车的实现:汽车、公共汽车、卡车、拖拉机等3. 很少有两轮车的实现:自行车、踏板车、自行车等。工厂设计模式完美地迎合了这些创建场景,并允许易于扩展、可维护性、关注点分离和单一职责。

为什么抽象工厂模式?
由于系统中有两个子实体系列:两轮车和四轮车及其进一步的实现也分别处理,并且对根对象属性具有一些相互依赖性,因此抽象工厂模式完美地位于此处。它还允许将来轻松扩展,例如:三轮车子系列、n-vehicle 子系列等。

验证类型
该设计将支持多种类型的验证:

属性级别的验证

  • 1. 检查可空性
  • 2. 检查支持的枚举中的有效值
  • 3. 检查值的范围


实体级别的验证可以为子实体设置验证链的独特且特定的实现:在这种情况下是两轮车和四轮车。责任链模式可以根据子实体的功能进行定制,并且可以在以下方面有所不同:
  • 1. 验证步骤的排序
  • 2. 验证步骤的具体实现
  • 3. 取值范围、类型检查、属性过滤


代码结构
1、定义验证器接口、验证器工厂和验证步骤

public  interface  VehicleValidator { 
  
  Optional<Throwable> validate (Vehicle vehicle) ; 
}

VehicleValidator是父接口,它将根据车辆系列进行实现,例如:TwoWheeler、FourWheeler 等。这将是车辆实体验证子模块的驱动接口。

下面是是VehicleValidatorFactory为不同类型的车辆家族生成验证器的代码工厂,例如:TwoWheeler, FourWheeler,等等。

public class VehicleValidatorFactory {

  private final TwoWheelerValidator twoWheelerValidator;
  private final FourWheelerValidator fourWheelerValidator;

  public VehicleValidator getValidator(VehicleType vehicleType) {
    switch (vehicleType) {
      case TWO_WHEELER:
        return twoWheelerValidator;
      case FOUR_WHEELER:
        return fourWheelerValidator;
      default:
        throw new BadRequestException(
          ErrorCode.BAD_REQUEST_UNSUPPORTED_VEHICLE_TYPE);
    }
  }


下面是ValidationStep是核心结构,用于创建车辆实体验证模块的责任链。

public abstract class ValidationStep<T> {
  
  private ValidationStep<T> next;

  public ValidationStep<T> linkWith(ValidationStep<T> next) {
    if (next == null) {
      this.next = next;
      return this;
    }
    this.next = next;
    return this;
  }

  public abstract Optional<Throwable> validate(T toValidate);

  protected Optional<Throwable> checkNext(T toValidate) {
    if (next == null) {
      return Optional.empty();
    }
    return next.validate(toValidate);
  }
}


2.验证步骤的实现
每个属性可以有多个ValidationStep的实现,以执行单一责任原则,并保持代码的可维护性、灵活性和易于扩展。

@Component
public class FuelTypeValidationStep extends ValidationStep<Vehicle> {
  
  @Override
  public Optional<Throwable> validate(Vehicle vehicle) {
    if (!Arrays.stream(FuelType.values()).anyMatch((t) ->
      t.name().equals(vehicle.getFuelType())) {
      return Optional.of(new BadRequestException(
        ErrorCode.BAD_REQUEST_UNSUPPORTED_FUEL_TYPE));
    } else {
      return checkNext(vehicle);
    }
  }
}


FuelTypeValidationStep是ValidationStep的实现之一,其结构遵循单一责任设计原则,因为它只包含车辆实体的fuelType属性的业务逻辑和约束。

DriveModeValidationStep也是ValidationStep的实现之一,它的结构遵循单一责任的设计原则,因为它只包含车辆实体的driveModesSupported属性的业务逻辑和约束。

下面VehicleDataValidationStep是ValidationStep的实现之一,它的结构遵循单一责任设计原则,因为它只包含车辆实体的vectorData属性的业务逻辑和约束。它利用VehicleDataValidatorFactory,而VehicleDataValidatorAbstractFactory的实现又可以根据车辆家族的燃料类型来决定要使用的确切验证器。

@Component
public class VehicleDataValidationStep extends ValidationStep<Vehicle> {

  private final VehicleDataValidatorFactory vehicleDataValidatorFactory;

  @Override
  public Optional<Throwable> validate(Vehicle vehicle) {
    var validatorFactory = vehicleDataValidatorFactory
        .getVehicleDataValidator(vehicle.getVehicleType());
    var channelValidator =  validatorFactory
      .getValidator(vehicle.getFuelType());
    return channelValidator.validateVehicleData(vehicle)
        .map(Optional::of)
        .orElseGet(() -> checkNext(vehicle));
  }
}


3、设置AbstractFactory和它的实现
VehicleDataValidatorAbstractFactory是一个抽象的工厂接口,用于为不同类型的车辆家族设置代码工厂,例如:TwoWheeler, FourWheeler等。

public interface VehicleDataValidatorAbstractFactory {

  ChannelValidator getValidator(VehicleType vehicleType);
}


VehicleDataValidatorFactory是为不同类型的车辆家族设置代码工厂实现的工厂,例如:TwoWheeler, FourWheeler等。

public class VehicleDataValidatorFactory {

  private final TwoWheelerVehicleDataValidatorFactory twoWheelerVehicleDataValidatorFactory;
  private final FourWheelerVehicleDataValidatorFactory fourWheelerVehicleDataValidatorFactory;

  public VehicleDataValidatorAbstractFactory getVehicleDataValidator(
    VehicleType vehicleType) {
    switch (vehicleType) {
      case TWO_WHEELER:
        return twoWheelerVehicleDataValidatorFactory;
      case FOUR_WHEELER:
        return fourWheelerVehicleDataValidatorFactory;
      default:
        throw new BadRequestException(
          ErrorCode.BAD_REQUEST_UNSUPPORTED_VEHICLE_TYPE);
    }
  }
}


下面FourWheelerVehicleDataValidatorFactory是一个抽象工厂的实现,用于为四轮车家族的不同类型的燃料生成验证器,例如:汽油、电动、柴油、压缩天然气等。

TwoWheelerVehicleDataValidatorFactory是另一个抽象工厂的实现(类似于上面的FourWheelerVehicleDataValidatorFactory),用于为两轮车族的不同燃料类型生成验证器,例如:汽油、电动等。

public class FourWheelerVehicleDataValidatorFactory implements 
  VehicleDataValidatorAbstractFactory {
  
  private final PetrolVehicleValidator petrolVehicleValidator;
  private final ElectricVehicleValidator electricVehicleValidator;
  private final DieselVehicleValidator dieselVehicleValidator;
  private final CngVehicleValidator cngVehicleValidator;

  @Override
  public VehicleDataValidator getValidator(FuelType fuelType) {
    switch (fuelType) {
      case PETROL:
        return petrolVehicleValidator;
      case ELECTRIC:
        return electricVehicleValidator;
      case CNG:
        return cngVehicleValidator;
      case DIESEL:
        return dieselVehicleValidator;
      default:
        throw new BadRequestException(
          ErrorCode.BAD_REQUEST_UNSUPPORTED_FUEL_TYPE);
    }
  }
}


4、验证步骤的链条化
FourWheelerValidator是一个驱动类,车辆实体的四轮车系列的验证步骤在这里发生连锁反应。在这里,用户可以根据业务逻辑和优化要求,定义车辆实体的四轮车系列属性的验证顺序。

@Component
public class FourWheelerValidator implements VehicleValidator {

  private final FuelTypeValidationStep fuelTypeValidationStep;
  private final DriveModeValidationStep driveModeValidationStep;
  private final VehicleDataValidationStep vehicleDataValidationStep;

  @PostConstruct
  void init() {
    fuelTypeValidationStep.linkWith(driveModeValidationStep);
    driveModeValidationStep.linkWith(vehicleDataValidationStep);
    vehicleDataValidationStep.linkWith(null);
  }

  @Override
  public Optional<Throwable> validate(Vehicle vehicle) {
    return fuelTypeValidationStep.validate(vehicle);
  }
}


下面是TwoWheelerValidator是一个驱动类,车辆实体的两轮车系列的验证步骤在此发生连锁。在这里,用户可以根据业务逻辑和优化要求,定义车辆实体的两轮车系列属性的验证顺序。

@Component
public class TwoWheelerValidator implements VehicleValidator {

  private final FuelTypeValidationStep fuelTypeValidationStep;
  private final DriveModeValidationStep driveModeValidationStep;
  private final VehicleDataValidationStep vehicleDataValidationStep;

  @PostConstruct
  void init() {
    driveModeValidationStep.linkWith(fuelTypeValidationStep);
    fuelTypeValidationStep.linkWith(vehicleDataValidationStep);
    vehicleDataValidationStep.linkWith(null);
  }

  @Override
  public Optional<Throwable> validate(Vehicle vehicle) {
    return driveModeValidationStep.validate(vehicle);
  }
}



5、定义车辆验证器接口和它的实现
VehicleDataValidator是所有燃料验证器都要扩展的接口。

public interface VehicleDataValidator {

  Optional<Throwable> validateVehicleData(Template template);

}


下面PetrolVehicleDataValidator是VehicleDataValidator的实现之一,它的结构遵循单一责任的设计原则,因为它只包含汽油车支持的车辆Data属性的业务逻辑和约束。也可以为不同系列的车辆单独实现PetrolVehicleDataValidator类,例如:TwoWheelerPetrolVehicleDataValidator,FourWheelerPetrolVehicleDataValidator,等等。

同样,对于其他车辆数据验证器的实现也是如此...

@Component
public class PetrolVehicleDataValidator implements VehicleValidator {

  @Override
  public Optional<Throwable> validateVehicleData(Vehicle vehicle) {
    // Custom logic
  }

}



6.执行验证子模块

通过在vehicleType的基础上创建VehicleValidatorFactory的实例并调用validate(),我们可以触发车辆实体的验证模块。

返回类型
这里,验证器类返回Optional<Throwable>,因为这被设计成一个快速失败的验证模块,在遇到第一个错误时就会中断,并返回给客户端。然而,这可以定制为抛出一个List<Optional<Throwable>>,因为验证模块可以被调整为运行所有的验证步骤,直到链的末端,而不会在返回错误的第一个验证步骤中断流程。

代码的可扩展性、可维护性和可扩展性
以验证模块的设计方式,它可以确保:

  • 易扩展性和可伸缩性,以纳入更多的车辆系列,例如:三轮车、N轮车。
  • 易于扩展性和可伸缩性,以纳入更多的燃料类型和它们的实现,例如:氢动力汽车,等等。
  • 易于维护验证器实现类,因为每个验证器实现类都专注于单一责任原则。在两个验证器类之间的代码中没有硬耦合。每个验证器类都有自己的属性验证逻辑,这就保证了在未来任何业务逻辑发生变化或得到增强时,代码的变化最小。
  • 易于扩展性和可伸缩性,以支持车辆实体的更多依赖属性。一个新的验证步骤可以用相应的业务逻辑来设置。
  • 易于调试代码,因为验证逻辑是高度解耦的。
  • 灵活地改变验证步骤的顺序,增强验证步骤中的业务逻辑,等等。