当前位置: 首页 > 知识库问答 >
问题:

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

孟凯泽
2023-03-14

我目前正在使用HibernateConstraintValidator来实现我的验证。但是我的审阅者不喜欢在代码或!运算符中使用if/其他。我可以使用哪种设计模式来删除验证逻辑中的if/其他?

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/否则

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
      }
}

附带说明:我们必须使用域驱动设计(如果有帮助的话)

共有2个答案

濮君植
2023-03-14
匿名用户

您可以检查空html" target="_blank">对象模式。一般模式是在代码中完全禁止< code>null。这消除了难看的< code>null检查。在这一点上,我同意你的代码审查。

遵循以下建议将导致:

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

在引入空对象模式之前,只需应用该模式或约定来强制初始化所有引用。这样,您可以确保整个代码中没有< code>null引用。< br >因此,当您遇到< code > NullPointerException 时,您不会通过引入< code>null检查来解决问题,而是通过初始化引用(在构造时)来解决问题,例如,通过使用默认值、空集合或空对象。

大多数现代语言都支持通过像< code>@NonNull这样的注释进行代码分析,这种注释检查像参数这样的引用,并在参数为< code > null /未初始化时抛出异常。例如,< code>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;
  }    
}

使用Null帐户类初始化所有帐户引用:

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):布尔实现。

最终的解决方案实现了Null Object模式并重构了find方法。原来的<code>findByAAndB()</code>的结果以前被丢弃了,因为您想测试<code>A</code>和<code>B</code>的存在性。另一种方法<code>public boolean contains()</code>将改进代码
重构的实现如下所示:

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);
}

厍彭薄
2023-03-14

很久以前,在时间的开端。有一个指导方针说方法应该只有一个出口点。为了实现这一点,开发人员必须跟踪本地状态,并使用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不存在,没关系,但如果存在,该项目可能不存在”。

或在代码中:

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。

    当你说话的时候试着写代码,这会让你更容易在脑海中描绘代码。你不是在说“我没吃饱,我们去吃午饭吧”,是吗?;)

 类似资料:
  • 在网上搜索和阅读了关于设计模式的文章后,我发现了三种类型,即行为型、创造型和结构型设计模式(GOF)。

  • 我编写了一个程序来解析XML文件,以获取一个名为SerialNum的特定标记值,该值包含在Header标记中。该文件的构造如下: < li >它包含一个标题和一个正文 < li >标题可能包含许多SerialNum标签。我们需要提取最后一个标签的值。 我使用Stax解析器获取SerialNum值,并编写了以下代码: 此代码提取所需的值,但代码质量不是很好:它不容易阅读和维护。它包含一个开关案例,如

  • 目前我有3个类,其中大多数变量是相同的。所有变量都有getter和setter。我需要根据提供的数据类型变量Year创建类的对象,并在十多个方法中传递该对象。让我们举个例子。 我们有三个班,A班,B班,C班。如果我们通过2017年,那么我需要ClassA的目标。如果我们通过2016年,那么我需要B类的目标,而对于所有其他年份,我们需要C类的目标。 然后,我们需要在所有10个方法中传递该对象。让我们

  • 在众所周知的设计书《Domain-Driven Terms》中,它被描述为: 设计模式是命名、抽象和识别对可重用的面向对象设计有用的的通用设计结构。设计模式确定类和他们的实体、他们的角色和协作、还有他们的责任分配。 每一个设计模式都聚焦于一个面向对象的设计难题或问题。它描述了在其它设计的约束下它能否使用,使用它后的后果和得失。因为我们必须最终实现我们的设计模式,所以每个设计模式都提供了例子..代码