如何从自己的验证器验证每个进程对象?

时间:2016-12-23 18:46:35

标签: java validation oop design-patterns

我有两个进程,对于每个进程,我将得到不同的Record对象,我需要验证那些Record对象。这意味着我不能使用单个验证器,因为我必须验证该过程的不同字段。

  • 对于processA,我使用ValidatorA类来验证其Record对象。
  • 对于processB,我使用ValidatorB类来验证其Record对象。

如果它们有效,那么我将继续前进,否则我将无法前进。以下是我和A和B的流程代码。

public class ProcessConsumer implements Runnable {
  private static final Logger logger = Logger.getInstance(ProcessConsumer.class);
  private final String processName;
  private final Validator validator;
  private final RecordProcessor<byte[], byte[]> process;

  public ProcessConsumer(String processName, Validator validator) {
    this.processName = processName;
    this.validator = validator;
    this.process = new RecordProcessor<>();
  }

  @Override
  public void run() {
    try {
      process.subscribe(getTopicsBasedOnProcessName(processName));
      ....

      while (true) {
        ConsumerRecords<byte[], byte[]> crs = process.poll(2000);
        for (ConsumerRecord<byte[], byte[]> cr : crs) {
          // record object will be different for my both the processes.
          Record record = decoder.decode(cr.value());
          Optional<DataHolder> validatedHolder = validator.getDataHolder(processName, record);
          if (!validatedHolder.isPresent()) {
            logger.logError("records dropped. payload= ", record);
            continue;
          }
          // send validatedHolder to processor class
          Processor.getInstance().execute(validatedHolder);
        }
      }
    } catch (Exception ex) {
      logger.logError("error= ", ExceptionUtils.getStackTrace(ex));
    }
  }
}

下面是我的ValidatorA课程,其中我在record对象上验证了几个字段,如果它有效,那么我将返回DataHolder

public class ValidatorA extends Validator {
  private static final Logger logger = Logger.getInstance(ValidatorA.class);

  @Override
  public static Optional<DataHolder> getDataHolder(String processName, Record record) {
    Optional<DataHolder> dataHolder = Optional.absent();
    if (isValid(processName, record))
      dataHolder = Optional.of(buildDataHolder(processName, record));
    return dataHolder;
  }

  private DataHolder isValid(String processName, Record record) {
    return isValidClientIdDeviceId(processName, record) && isValidPayId(processName, record)
        && isValidHolder(processName, record)
  }  

  private DataHolder buildDataHolder(String processName, Record record) {
    Map<String, String> holder = (Map<String, String>) DataUtils.extract(record, "holder");
    String deviceId = (String) DataUtils.extract(record, "deviceId");
    Integer payId = (Integer) DataUtils.extract(record, "payId");
    String clientId = (String) DataUtils.extract(record, "clientId");

    // add mandatory fields in the holder map after getting other fields
    holder.put("isClientId", (clientId == null) ? "false" : "true");
    holder.put("isDeviceId", (clientId == null) ? "true" : "false");
    holder.put("abc", (clientId == null) ? deviceId : clientId);

    return new DataHolder.Builder(record).setClientId(clientId).setDeviceId(deviceId)
        .setPayId(String.valueOf(payId)).setHolder(holder).build();
  }

  private boolean isValidHolder(String processName, Record record) {
    Map<String, String> holder = (Map<String, String>) DataUtils.extract(record, "holder");
    if (MapUtils.isEmpty(holder)) {
      logger.logError("invalid holder is coming.");
      return false;
    }
    return true;
  }

  private boolean isValidpayId(String processName, Record record) {
    Integer payId = (Integer) DataUtils.extract(record, "payId");
    if (payId == null) {
      logger.logError("invalid payId is coming.");
      return false;
    }
    return true;
  }

  private boolean isValidClientIdDeviceId(String processName, Record record) {
    String deviceId = (String) DataUtils.extract(record, "deviceId");
    String clientId = (String) DataUtils.extract(record, "clientId");
    if (Strings.isNullOrEmpty(clientId) && Strings.isNullOrEmpty(deviceId)) {
      logger.logError("invalid clientId and deviceId is coming.");
      return false;
    }
    return true;
  }
}

以下是我的ValidatorB课程,与ValidatorA对象上的record相比,我在其中验证了几个不同的字段,如果它有效,那么我将返回{{1} }。

DataHolder

以下是我的抽象类:

public class ValidatorB extends Validator {
  private static final Logger logger = Logger.getInstance(ValidatorB.class);

  @Override
  public static Optional<DataHolder> getDataHolder(String processName, Record record) {
    Optional<DataHolder> dataHolder = Optional.absent();
    if (isValid(processName, record))
      dataHolder = Optional.of(buildDataHolder(processName, record));
    return dataHolder;
  }

  private DataHolder isValid(String processName, Record record) {
    return isValidType(processName, record) && isValidDatumId(processName, record) && isValidItemId(processName, record);
  }  

  private DataHolder buildDataHolder(String processName, Record record) {
    String type = (String) DataUtils.extract(record, "type");
    String datumId = (String) DataUtils.extract(record, "datumId");
    String itemId = (String) DataUtils.extract(record, "itemId");

    return new DataHolder.Builder(record).setType(type).setDatumId(datumId)
        .setItemId(itemId).build();
  }


  private boolean isValidType(String processName, Record record) {
    String type = (String) DataUtils.extract(record, "type");
    if (Strings.isNullOrEmpty(type)) {
      logger.logError("invalid type is coming.");
      return false;
    }
    return true;
  }  

  private boolean isValidDatumId(String processName, Record record) {
    String datumId = (String) DataUtils.extract(record, "datumId");
    if (Strings.isNullOrEmpty(datumId)) {
      logger.logError("invalid datumId is coming.");
      return false;
    }
    return true;
  }   

  private boolean isValidItemId(String processName, Record record) {
    String itemId = (String) DataUtils.extract(record, "itemId");
    if (Strings.isNullOrEmpty(itemId)) {
      logger.logError("invalid itemId is coming.");
      return false;
    }
    return true;
  }
}

问题:

这就是我要求我的两个过程。如您所见,我在构造函数argumnets中传递public abstract class Validator { public abstract Optional<DataHolder> getDataHolder(String processName, Record record); } 及其特定验证器。

processName
  • 这是一个很好的设计,对于我的每个进程,将其验证器与?
  • 一起传递
  • 我们有什么方法可以避免通过那个?并在内部找出ProcessConsumer processA = new ProcessConsumer("processA", new ValidatorA()); ProcessConsumer processB = new ProcessConsumer("processB", new ValidatorB()); 使用哪些验证器?我已经拥有了所有processName的枚举。我需要使这个设计具有可扩展性,这样如果我将来添加新进程,它应该是可扩展的。
  • 另外,我的抽象类processName的方式是对的吗?它完全没有做任何有用的事情。

我的每个Validator基本上都在尝试验证Validator对象是否有效。如果它们有效,则它们会生成record构建器并将其返回,否则返回DataHolder;

我看到了post他们谈到使用Optional.absent()的地方,但我不确定在这种情况下这对我有什么帮助。

2 个答案:

答案 0 :(得分:1)

首先,当我看到声明及其实施时:

public abstract class Validator {
  public abstract Optional<DataHolder> getDataHolder(String processName, Record record);
}

我不认为“验证者”是最好的术语。您的验证人不仅是验证人。您称为验证器的主要功能是:为特定进程提取数据。提取需要验证,但它不是主要功能 验证器的主要功能是验证 所以我认为你可以称之为:ProcessDataExtractor。

ProcessConsumer processA = new ProcessConsumer("processA", new ValidatorA());
ProcessConsumer processB = new ProcessConsumer("processB", new ValidatorB());
  

这是一个很好的设计,我的每个进程都通过它的验证器   随着?有什么方法可以避免传递吗?在内部   找出在processName上使用哪些验证器?我已经   有一个包含我所有processName的枚举。我需要做这个设计   可扩展,这样如果我将来添加新进程,它应该是   可扩展性。

可扩展性是另一回事 具有可扩展的设计通常具有一种设计,一旦在应用的生命周期中发生新的“正常”要求,该设计并不意味着重要且有风险的修改。
如果添加了新的流程使用者,则必须根据需要添加ProcessDataExtractor。客户应该知道这个新的流程消费者。
如果客户端代码在编译时实例化其进程使用者及其数据提取器,则使用enum和map来表示进程使用者和数据提取器不会使您的设计无法扩展,因为它只需要很少的修改并且这些是孤立的

如果您希望在代码中进行更少的修改,可以通过反射提取器实例化并使用命名约定来检索它们。
例如,将它们始终放在同一个包中,并使用相同的前缀命名每个提取器,例如:ProcessDataExtractorXXXXXX是变量部分。
此解决方案的问题是在编译时:客户端不知道ProcessDataExtractor可用的必要。

如果您希望将新的流程使用者和提取者添加为动态的,即在应用程序的运行时期间以及客户端也可以在运行时检索它们,那么我认为这是另一个主题。

在编译时,设计可能会更好,因为到目前为止ProcessConsumerProcessDataExtractor类的客户端可能会错误地使用它们(即Process A使用ProcessDataExtractor B {1}})。
为避免这种情况,您有几种方法可做 但您已经猜到了这个想法:在专用位置和受保护的方式中进行初始化以及ProcessConsumerProcessDataExtractor之间的映射。

为了实现它,我建议你介绍ProcessConsumer的接口,它只提供Runnable的功能方法:

public interface IProcessConsumer extends Runnable {

}

从现在开始,想要使用进程的客户端应该只使用此接口来执行其任务。
我们不希望提供给客户端方法或构造函数来选择其数据提取器 要做到这一点,具体的ProcessConsumer类应该是一个内部私有类,以便不允许客户端直接实例化它。
他们将不得不使用工厂来满足这种需求 通过这种方式,客户端能够通过请求进程工厂来创建具有所需数据提取器的特定进程使用者,这些进程负责确保数据提取器和进程之间的一致性,并且还保证新进程使用者的实例化。每次调用(您的流程都是有状态的,因此您必须为您启动的每个流程消费者创建一个新的流程消费者。)

以下是ProcessConsumerFactory类:

import java.util.HashMap;
import java.util.Map;

public class ProcessConsumerFactory {

    public static enum ProcessType {

      A("processA"), B("processB");

      private String name;

      ProcessType(String name) {
        this.name = name;
      }

      public String getName() {
        return name;
      }
    }

    private class ProcessConsumer implements IProcessConsumer {

      private final ProcessType processType;
      private final ProcessDataExtractor extractor;
      private final RecordProcessor<byte[], byte[]> process;

      public ProcessConsumer(ProcessType processType, ProcessDataExtractor extractor) {
        this.processType = processType;
        this.extractor = extractor;
        this.process = new RecordProcessor<>();
      }

      @Override
      public void run() {
         // your implementation...
      }
    }

    private static ProcessConsumerFactory instance = new ProcessConsumerFactory();

    private Map<ProcessType, ProcessDataExtractor> extractorsByProcessName;

    private ProcessConsumerFactory() {
      extractorsByProcessName = new HashMap<>();
      extractorsByProcessName.put(ProcessType.A, new ProcessDataExtractorA());
      extractorsByProcessName.put(ProcessType.B, new ProcessDataExtractorB());
      // add a new element in the map to add a new mapping
    }

    public static ProcessConsumerFactory getInstance() {
      return instance;
    }

    public IProcessConsumer createNewProcessConsumer(ProcessType  processType) {
      ProcessDataExtractor extractor = extractorsByProcessName.get(processType);
      if (extractor == null) {
        throw new IllegalArgumentException("processType " + processType + " not recognized");
      }
      IProcessConsumer processConsumer = new ProcessConsumer(processType, extractor);
      return processConsumer;
    }

}

现在,Process消费者类的客户端可以像这样实现它们:

IProcessConsumer processConsumer = ProcessConsumerFactory.getInstance().createNewProcessConsumer(ProcessType.A);
  

另外我的抽象类Validator的方式是对的吗?它不是   做任何有用的事情看起来都像。

您使用抽象类作为验证器,但目前您还没有在此抽象类中移动常见行为,因此您可以使用接口:

public interface ProcessDataExtractor{
  Optional<DataHolder> getDataHolder(ProcessType processType, Record record);
}

如果它变得合适,你可以稍后介绍抽象类。

答案 1 :(得分:0)

您的设计存在一些问题:

尽早抓住无效数据。

施工后不是正确的方法。一旦构造了一个对象(在本例中为Record),它就应该具有有效状态。这意味着,您的验证应该在构建Record之前执行。

  1. 从某处获取数据:来自网络,数据库,文本文件,用户输入或其他任何内容。
  2. 验证数据。
  3. 构建Record对象。此时,Record对象具有有效状态,或者例如通过引发异常而失败构造。
  4. 现在,如果从中获取数据的源,主要包含无效数据,则应该在那里处理。因为这本身就是一个问题。如果源是正确的但是读取或获取数据有问题,应首先解决。

    假设存在上述问题,如果存在,则解决,那么程序的顺序应该是这样的。

    // Get the data from some source
    // Perform Validation on the data. This is generic validation, like validation // of data read from an input form etc.
    validate deviceId
    validate payId 
    validate clientId 
    ...
    if invalid do something
    else if valid proceed
    
    // construct Record object
    Record record = new Record(deviceId, payId, clientId, ...)
    // At this point record has valid data
    
    public class Record {
    
     deviceId
     payId 
     clientId 
     Record(deviceId, payId, clientId, ...) {
        // Perform business rule validation, pertaining to Record's requirements.
        // For example, if deviceId must be in certain range etc.
    
        // if data is valid, perform construction.
        // else if invalid, don't construct. throw exception or something 
        // to deal with invalid condition
     }
    }
    

    另一个问题是,您使用一些utils类从Record中提取内部数据。那也不对。记录本身应该为其属性提供getter。现在,与Record有关的内容分散在 记录,实用程序和验证器。

    我认为您的代码需要彻底的重新评估。我建议,暂停,重新开始,但这次是在更高的水平。在没有代码的情况下开始设计,例如使用某种图表。从盒子和箭头开始(类似于类图,但不需要使用UML工具等。铅笔和纸。决定应该去哪里。比如,

    1. 您正在处理的实体是什么。
    2. 每个实体具有哪些属性,属性,方法等。
    3. 这些实体之间的关系是什么
    4. 考虑使用或交互这些实体的顺序,然后不断完善它。
    5. 如果没有这种高级视图,在代码级别处理设计问题很困难,而且几乎总是会产生不良结果。

      一旦你处理了更高层次的设计。然后,将它放在代码中要容易得多。当然,您也可以在该级别进行优化,但应首先考虑高级结构。