java—在验证类中使用哪种设计模式来避免if/else?

ryoqjall  于 2021-07-08  发布在  Java
关注(0)|答案(2)|浏览(600)

我目前正在使用hibernateconstraintvalidator来实现我的验证。但是我的审稿人不喜欢在代码中使用if/else或!操作员。我可以使用哪种设计模式删除验证逻辑中的if/else?

public class SomeValidatorX implements ConstraintValidator<SomeAnnotation, UUID> {

      @Autowired
      SomeRepository someRepository;

      @Override
      public boolean isValid(UUID uuid, ConstraintValidationContext context) {

             return !(uuid!=null && someRepository.existsById(uuid)); //The reviewer doesn't want this negation operator
      }
}

在下面的代码中,他不想要if/else

public class SomeValidatorY implements ConstraintValidator<SomeAnnotation, SomeClass> {

      @Autowired
      SomeRepository someRepository;

      @Override
      public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
           if(someObject.getFieldA() != null) { //He doesn't want this if statement
                //do some operations
                List<Something> someList = someRepository.findByAAndB(someObject.getFieldA(),B);
                return !someList.isEmpty(); //He doesn't want this ! operator
           }
           return false; // He was not fine with else statement in here as well
      }
}

旁注:我们必须使用域驱动设计(如果有帮助的话)

cngwdvgl

cngwdvgl1#

可以检查空对象模式。一般的模式是禁止 null 完全脱离你的代码。这就消除了丑陋 null 检查。在这一点上,我同意你的代码审查。
以下建议将导致:

public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
  return someRepository.containsAAndB(someObject.getFieldA(), B);
}

避免空检查

在引入空对象模式之前,只需应用模式或约定来强制初始化所有引用。这样你就可以确定 null 整个代码中的引用。
所以当你遇到 NullPointerException ,你不能通过引入 null 检查,但通过初始化引用(在构造时),例如使用默认值、空集合或空对象。
大多数现代语言都支持通过注解进行代码分析,比如 @NonNull 它检查参数之类的引用,并在参数为 null /未初始化。 javax.annotation 例如,提供了这样的注解。

public void operation(@NonNull Object param) {
    param.toString(); // Guaranteed to be not null
}

使用这样的注解可以保护库代码不受空参数的影响。

空对象模式

而不是 null 引用时,可以使用有意义的值或专用的空对象初始化每个引用:
定义空对象协定(不是必需的):

interface NullObject {
  public boolean getIsNull();
}

定义基类型:

abstract class Account {
  private double value;
  private List<Owner> owners;

  // Getters/setters    
}

定义空对象:

class NullAccount extends Account implements NullObject {

  // Initialize ALL attributes with meaningful and *neutral* values
  public NullAccount() {
    setValue(0); // 
    setOwners(new ArrayList<Owner>())

  @Override
  public boolean getIsNull() {
    return true;
  }
}

定义默认实现:

class AccountImpl extends Account implements NullObject {

  @Override
  public boolean getIsNull() {
    return true;
  }    
}

全部初始化 Account 使用 NullAccount 班级:

class Employee  {
  private Account Account;

  public Employee() {
    setAccount(new NullAccount());
  }
}

或者使用 NullAccount 返回失败的状态示例(或默认值),而不是返回 null :

public Account findAccountOf(Owner owner) {
  if (notFound) {
    return new NullAccount();
  }
}

public void testNullAccount() {
  Account result = findAccountOf(null); // Returns a NullAccount

  // The Null-object is neutral. We can use it without null checking.
  // result.getOwners() always returns 
  // an empty collection (NullAccount) => no iteration => neutral behavior
  for (Owner owner : result.getOwners()) {
    double total += result.getvalue(); // No side effect.
  }
}

试做模式

您可以使用的另一个模式是try-do模式。您只需测试操作本身,而不是测试操作的结果。操作负责返回操作是否成功。
在文本中搜索字符串时,无论结果是否找到,都可以返回布尔值,而不是返回空字符串,甚至更糟 null :

public boolean tryFindInText(String source, String searchKey, SearchResult result) {
  int matchIndex = source.indexOf(searchKey);
  result.setMatchIndex(matchIndex);
  return matchIndex > 0;
}

public void useTryDo() {
  SearchResult result = new Searchresult();
  if (tryFindInText("Example text", "ample", result) {
    int index = result.getMatchIndex();
  }
}

在您的特殊情况下,您可以更换 findByAAndB() 带着一个 containsAAndB() : boolean 实施。

组合模式

最终的解决方案实现空对象模式并重构find方法。原来的结果 findByAAndB() 因为你想测试 A 以及 B . 另一种方法 public boolean contains() 将改进您的代码。
重构后的实现如下所示:

abstract class FieldA {

}

class NullFieldA {

}

class FieldAImpl {

}

class SomeClass {

  public SomeClass() {
    setFieldA(new NullFieldA());
  }
}

改进的验证:

public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
  return someRepository.containsAAndB(someObject.getFieldA(), B);
}
v7pvogib

v7pvogib2#

很久以前,在时间的开始。有一个指导方针说方法应该只有一个出口点。为了实现这一点,开发人员必须跟踪本地状态并使用if/else才能到达方法的末尾。
今天我们更清楚了。通过尽可能早地退出一个方法,在阅读代码时将整个流程保持在我们的头脑中会容易得多。更简单的代码意味着更少的错误。更少的错误等于更少的错误。
在我看来,这就是为什么评审员不喜欢代码。它不是那么容易阅读,因为它可以。
我们举第一个例子:

public boolean isValid(UUID uuid, ConstraintValidationContext context) {

         return !(uuid!=null && someRepository.existsById(uuid)); //The reviewer doesn't want this negation operator
  }

代码说的是“不是这个:(uuid不应该是空的,它必须存在)”。这容易理解吗?我想不是。
另一种选择是:“如果uuid不存在就可以了,但是如果uuid存在,则该项可能不存在”。
或在代码中:

if (uuid == null) return true;
return !someRepository.existsById(uuid);

更容易阅读,对吧(我希望我的意图是正确的;))
第二个例子

if(someObject.getFieldA() != null) { //He doesn't want this if statement
            //do some operations
            List<Something> someList = someRepository.findByAAndB(someObject.getFieldA(),B);
            return !someList.isEmpty(); //He doesn't want this ! operator
       }
       return false; // He was not fine with else statement in here as well

好 啊。你在说:
如果字段a不为空:
在找到a和b的地方建立一个列表
如果该列表不为空,则失败,否则成功。
否则失败
一个简单的结论是:
如果没有指定字段a就可以了
如果指定了字段a,则它必须与b一起存在。
翻译成代码:

if (someObject.getFieldA() == null) 
    return true;

return !someRepository.findByAAndB(someObject.getFieldA(),B).isEmpty();

在c#我们有 Any() 它与 isEmpty 在这种情况下,我更喜欢它,因为它消除了否定。
有时需要否定。在存储库中编写新方法来避免它是没有意义的。但是,如果 findByAAndB 仅用于此,我将其重命名为 ensureCombination(a,b) 以便在有效情况下返回true。
试着边说边写代码,这样就更容易在脑海中描绘代码了。你不是说“我没吃饱,我们去吃午饭吧”,是吗?;)

相关问题