针对第三类场景,我们必须支持一种数据对应有多套业务校验规则,我们可以把每种业务规则都建模成一个校验规则类,但这样做灵活性和可扩展性就很差了。如果我们对一种数据只用一个校验规则类,而将多套业务规则建模成多种策略,在校验规则类中应用这些策略,这样做好处在于:一可以对用户隐藏业务规则,二是将来对策略进行修改或增加新的策略都不需要更改用户的调用接口,三是我们可以在运行时动态地改变业务规则-策略。要实现上述需求,我们只需要在可组装校验器架构的基础上,对校验规则 rule 类引入 Strategy 模式。首先我们设计策略类,对于银行并购这个例子,对帐号的校验可以分成以下几步:校验银行代号,地区代号,网点代号,用户代号,因此在这里我们先根据各个银行的业务策略对帐号进行分割,比如 19 位的帐号分成 4 位银行代号,4 位地区代号,4 位网点代号,和 7 位用户代号,然后再执行上述几步校验。业务策略类的接口和实现代码如下:
清单7:IAccountStrategy.java
2
3 /**
4
5 * 获得字符串分割规则,比如19位的帐号分成{4,4,4,7}。
6
7 */
8
9 public int[] getSeperatedNumbers();
10
11 /**
12
13 * 校验银行代号。
14
15 */
16
17 public boolean validateBankCode(String bankCode);
18
19 /**
20
21 * 校验地区代号。
22
23 */
24
25 public boolean validateDistrictCode(String districtCode);
26
27 /*校验网点代号。*/
28
29 public boolean validateSiteCode(String siteCode);
30
31 /*校验用户代号。*/
32
33 public boolean validateUserCode(String userCode);
34
35 }
36
37
清单8:BankAAccountStrategy.java
2
3 private static int[] seperatedNumbers = { 4, 4, 4, 4 };
4
5 public int[] getSeperatedNumbers() {
6
7 return seperatedNumbers;
8
9 }
10
11 public boolean validateBankCode(String bankCode) {
12
13 // query bank id from local database or meta-data file.
14
15 String bankID = "9880";
16
17 if (bankID.equals(bankCode))
18
19 return true;
20
21 return false;
22
23 }
24
25 public boolean validateDistrictCode(String districtCode) {
26
27 if ((districtCode != null)
28
29 && (districtCode.length() == seperatedNumbers[1]))
30
31 if (districtCode.startsWith("8"))
32
33 return true;
34
35 return false;
36
37 }
38
39 public boolean validateSiteCode(String siteCode) {
40
41 if ((siteCode != null) && (siteCode.length() == seperatedNumbers[2]))
42
43 if (siteCode.startsWith("1"))
44
45 return true;
46
47 return false;
48
49 }
50
51 public boolean validateUserCode(String userCode) {
52
53 if ((userCode != null) && (userCode.length() == seperatedNumbers[3]))
54
55 return true;
56
57 return false;
58
59 }
60
61 }
62
63
接下来我们设计校验规则类,该类主要有选择策略和使用策略来校验两种职责,代码如下:
清单9:
2
3 private static Map strategies = new HashMap();
4
5 static { //注册策略类
6
7 strategies.put(new Integer(16), new BankAAccountStrategy());
8
9 strategies.put(new Integer(19), new BankBAccountStrategy());
10
11 }
12
13 public void validate(Object value) throws DataValidationException {
14
15 if (value == null)
16
17 throw new DataValidationException("Account can't be empty.");
18
19 if (!(value instanceof String))
20
21 throw new DataValidationException("Can't cast Object to String.");
22
23 String val = (String) value;
24
25 if (strategies.containsKey(new Integer(val.length()))) {
26
27 IAccountStrategy strat = (IAccountStrategy) strategies
28
29 .get(new Integer(val.length()));
30
31 int[] sepNum = strat.getSeperatedNumbers();
32
33 //validate bank code
34
35 String bankCode = val.substring(0, sepNum[0]);
36
37 if (!strat.validateBankCode(bankCode))
38
39 throw new DataValidationException("Bank code " + bankCode
40
41 + " doesn't match account rule");
42
43 //validate district code.
44
45 String disCode = val.substring(sepNum[0], sepNum[0] + sepNum[1]);
46
47 if (!strat.validateDistrictCode(disCode))
48
49 throw new DataValidationException("District code " + disCode
50
51 |-------10--------20--------30--------40--------50--------60--------70--------80--------9|
52
53 |-------- XML error: The previous line is longer than the max of 90 characters ---------|
54
55 + " doesn't match account rule");
56
57 //validate site code
58
59 String siteCode = val.substring(sepNum[0] + sepNum[1], sepNum[0]
60
61 + sepNum[1] + sepNum[2]);
62
63 if (!strat.validateSiteCode(siteCode))
64
65 throw new DataValidationException("Site code " + siteCode
66
67 + " doesn't match account rule");
68
69 //validate user code
70
71 String userCode = val.substring(val.length() - sepNum[3]);
72
73 if (!strat.validateUserCode(userCode))
74
75 throw new DataValidationException("User code " + userCode
76
77 + " doesn't match account rule");
78
79 } else
80
81 throw new DataValidationException("The length of input account NO "
82
83 |-------10--------20--------30--------40--------50--------60--------70--------80--------9|
84
85 |-------- XML error: The previous line is longer than the max of 90 characters ---------|
86
87 + val.length() + " doesn't match account rule.");
88
89 }
90
91 }
92
93
在这里我们再一次用到了 Registry 模式,可以看到这个校验规则类具有管理和缓存策略类的职责。当然我们还可以在该类中增加一个方法 regiesterStrategy() 用来在运行时动态地增加业务规则策略,但目前我们的应用没有这么复杂的需求,就算将来有也很容易重构目前的架构,所以这个设计活动应该点到为止。这也是设计中的一个权衡点,设计是没有绝对完美的,人们在追逐绝对完美设计的过程中经常把对未来的种种揣测当作真正的需求,结果只能是危及整个设计,导致代码臃肿,难以维护,僵化,灵活性差。适度的设计才是完美的。
动态策略校验器的架构图如下:
图 2:可组装校验器的架构图

在本例中,我们并不是从整合遗留资产的角度出发的,在实际的例子中,银行A和银行B可能都已存在各自的校验类,这些类的接口不会是一致的,而且返回类型可能是boolean也可能是异常,甚至银行A是Java应用而银行B是C应用,这样的话我们必须将这些遗留应用中已有的校验类适配成现在的接口,这里可以对具体的Strategy实现类应用Adapter模式。