Validation Refactor -- Command Pattern

曾经遇到过几次如下的代码

1
2
3
4
5
6
7
8
9
10
11
12
public void validate(Person person, List<String> errors) {
  if (!validateName(person.getName())) {
    return;
  }
  if (!validatateAge(person.getAge())) {
    return;
  }
  if (!validateHeight(person.getHeight())) {
    return;
  }
  System.out.println("Validation Success!");
}

这一个Validator中的方法,对Person对象的三个属性进行校验,当某个属性失败时,将错误信息加入errors中,然后停止校验。 这是个简单的例子,可以想象,当对象比较复杂,属性很多时,此方法将会相应增加很多if和return,非常难看。由于每个属性的校验都有return语句,所以无法通过抽取方法来重构。今天米高告诉了一种方案,经过验证,效果不错。 方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface StopableMethod {
  boolean excute();
}

public class Excutor {
  private List<StopableMethod> methods;

  public Excutor(List<StopableMethod> methods) {
    this.methods = methods;
  }

  public void excute(){
    for (StopableMethod method : methods) {
      if(!method.excute()){
        break;
      }
    }
  }
}

public class ValidateName implements StopableMethod {
  private String name;

  public ValidateName(String name) {
    this.name = name;
  }

  public boolean excute() {
    if (name == null) {
        System.out.println("Name invalid.");
        return false;
    }
  return true;
  }
}

其中牵扯了三个部分

  • StopableMethod:所有验证方法实现的接口
  • Excutor:遍历执行所有方法,方法返回false则终止
  • ValidateName:校验Name的方法,实现上述接口。每个属性的校验方法都按照这个类实现

重构后调用的代码如下

1
2
3
4
5
6
7
8
9
@Test
public void should_fail_fast_when_validation_error() {
  person.setName(null);
  person.setAge(0);
  List<StopableMethod> methodList = new ArrayList<StopableMethod>();
  methodList.add(new ValidateName(person.getName()));
  methodList.add(new ValidateAge(person.getAge()));
  new Excutor(methodList).excute();
}

调用时,将所有验证方法的List传递给Excutor,执行excute即可。这样重构虽然增加了很多validate类,但是每个都很小,测试很简单,而且在测试中互相没有任何影响。

实际上,这是Command Pattern的一种实现,典型的Command Pattern结构如下

其中的对应关系为

  • Command:StopableMethod
  • Concrete: ValidateName
  • Receiver: Person
  • Invoker: Excuter
  • Client: Test方法

Command Pattern通常应用于以队列或堆栈的形式保存一组命令对象的场景,如:Multi-level undo,Transactional behavior, Progress bars, Wizards, GUI buttons and menu items, Thread pools, Macro recording, Networking, Parallel Processing, Mobile Code。

From: http://en.wikipedia.org/wiki/Command_pattern

comments powered by Disqus