在某些情况下,我们需要先验证输入数据,然后再将其发送到业务逻辑层进行处理,计算等。这种验证在大多数情况下是孤立完成的,或者可能包括与外部数据或其他输入的某些交叉检查。 看下面的示例,该示例验证用户输入的注册数据。
public void register(String email, String name, int age) {
String EMAIL_PATTERN =
"^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
Pattern pattern = Pattern.compile(EMAIL_PATTERN);
List<String> forbiddenDomains = Arrays.asList("domain1", "domain2");
if ( email == null || email.trim().equals("")){
throw new IllegalArgumentException("Email should not be empty!");
}
if ( !pattern.matcher(email).matches()) {
throw new IllegalArgumentException("Email is not a valid email!");
}
if ( forbiddenDomains.contains(email)){
throw new IllegalArgumentException("Email belongs to a forbidden email");
}
if ( name == null || name.trim().equals("")){
throw new IllegalArgumentException("Name should not be empty!");
}
if ( !name.matches("[a-zA-Z]+")){
throw new IllegalArgumentException("Name should contain only characters");
}
if ( age <= 18){
throw new IllegalArgumentException("Age should be greater than 18");
}
// More code to do the actual registration
}
这种方法的循环复杂性确实很高,如果要验证的字段更多或添加实际的业务逻辑,则可能会变得更糟。 当然,我们可以将代码分为两个私有方法(validate,doRegister),但是几个if块将被移到私有方法的问题。 除此以外,该方法还要做很多事情,而且很难测试。 当我要求初级开发人员重构该代码并使之更具可读性,可测试性和可维护性时,他们像个外星人一样看着我:“我应该如何简化它。 我该如何替换这些if块?” 好了,这里的解决方案效果很好,遵循“单一责任模式”,使代码更易于阅读。
为了更好地理解解决方案,请将每个if块都视为验证规则。 现在是时候为这些规则建模了。
首先用一种方法创建一个接口。 用Java 8术语,它称为功能接口 ,如下所示。
public interface RegistrationRule{
void validate();
}
现在是时候将每个验证检查转换为注册规则了。 但是在我们这样做之前,我们需要解决一个小问题。 我们的界面实现应该能够处理注册数据,但是如您所见,我们拥有不同类型的数据。 因此,我们这里需要将注册数据封装在单个对象中,如下所示:
public class RegistrationData{
private String name;
private String email;
private int age;
// Setters - Getters to follow
}
现在我们可以改善功能界面:
public interface RegistrationRule{
void validate(RegistrationData regData);
}
并开始编写我们的规则集。 例如,让我们尝试实现电子邮件验证。
public class EmailValidatationRule implements RegistrationRule{
private static final String EMAIL_PATTERN =
"^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
private final Pattern pattern = Pattern.compile(EMAIL_PATTERN);
@Override
public void validate(RegistrationData regData) {
if ( !pattern.matcher(regData.email).matches()) {
throw new IllegalArgumentException("Email is not a valid email!");
}
}
显然,我们在上述类中隔离了电子邮件验证。 我们可以对初始实施的所有规则执行相同的操作。 现在,我们可以重新编写我们的注册方法以使用验证规则。
List<RegistrationRule> rules = new ArrayList<>();
rules.add(new EmailValidatationRule());
rules.add(new EmailEmptinessRule());
rules.add(new ForbiddenEmailDomainsRule());
rules.add(new NameEmptinessRule());
rules.add(new AlphabeticNameRule());
for ( RegistrationRule rule : rules){
rule.validate(regData);
}
为了变得更好,我们可以使用Factory模式和一个静态方法get()创建一个Rules类,该方法将返回规则列表。 我们的最终实现将如下所示
for ( RegistrationRule rule : Rules.get()){
rule.validate(regData);
}
将我们的注册方法的初始版本与最终版本的注册方法进行比较会产生疑问。 我们的新版本更紧凑,更易读,当然也更具可测试性。 实际的检查已移至单独的类(也易于测试),并且所有方法仅做一件事(请始终牢记这一点)。
翻译自: https://www.javacodegeeks.com/2014/07/avoiding-many-if-blocks-for-validation-checking.html