前言
这篇文章来聊聊行为型模式中另一个经典模式。这个模式在代码结构设计用得非常之多。比如在servlet里的filter,hibernate的Validator,流程审批。通过学习filter与validator的实现代码,在实际的业务场景中应用责任链模式。也可以在学习后,分析现有系统中的腐味代码将其转换成责任链模式,为自己编程技巧加分。
本节目录
- 学习目标
- 概念:
- 记忆关键点:
- 实现步骤
- 类结构图
- 相似模式,区分及应用
- 扩展联想
- 实例
- hibernate-validator的框架思想
- 如何运用责任链模式
- 老破旧转化
- 回顾、总结
学习目标
1、认识责任链模式的结构。
2、在业务中使用责任链模式,加深对责任链模式概念的理解。
3、关注代码结构是如何来达到单一职责原则、隔离、开闭原则。
概念:
将请求的每个处理对象连接在一起组成一条链条,让链条中的每个处理对象至少或只有一次机会处理这个请求数据,并返回处理结果。
变体
将请求的每个处理对象连接在一起组成一条链条,直到被链条中的某个对象处理这个请求为止,并返回处理结果。
在下面的实例中讲解经典的j2ee filter和hibernate的validator框架,进而加深理解概念
记忆关键点:
处理对象组合成一条链条
实现步骤
- 将每步处理代码转换成处理对象
- 将处理对象加入到链条中去
- 客户端代码调用处理链条
类结构图
ChainProcessor中持有一个处理对象的链条List。AbstractChain的每一个不同实现都被加入到这个List中,当Client使用ChainProcessor.validator()方法里,ChainProcessor将处理委派给链条中的每一个对象进行不同的验证处理,全部验证通过则流程进行下一步业务处理。
相似模式,区分及应用
命令模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,并支持可撤销。
区别:命令模式是将一种请求跟一个处理对象逻辑关联起来,每个处理对象并未组合成一条链条。若果将这些处理对象组合成链条,即变为责任链模式。
模板方法模式:概念定义参考《设计模式应用实例一(模板方法模式的应用)》 区别:同样地将模板方法模式的每一个实现类组合成链条的话,模板方法模式即变为责任链模式
除以上两种模式外,将其它模式组合成链变成责任链模式将增加程序复杂度反而不利于维护和扩展。
责任链模式常见的用于权限过滤,参数验证,敏感词过滤
扩展联想
本模式实现上是要求程序设计者定义一种过滤规则,利用责任链模式将客户请求与过滤规则对比,将符合规则的结果返回给客户。
实例
先讲解hibernate-validator框架的大致实现思想。了解完hibernate-validator框架实现思想,我们再针对一些老破旧项目代码进行结构优化从而加深对责任链模式的理解及应用。
hibernate-validator的框架思想
源码取自hibernate-validator-6.0.18
程序执行时首先org.hibernate.validator.HibernateValidator加载,由它来提供ValidatorFactory–new ValidatorFactoryImpl,ValidatorFactoryImpl产生ValidatorImpl。这个ValidatorImpl读取所有bean的注解,将注解及其bean关联起来,同时生成这个bean的元数据约束,将元数据约束放到List中
这些元数据约束是什么?
就是@NotEmpty(message=“约束错误信息”)这些东西,与bean及bean的方法关系起来存入ValidationContext。作者将这些数据看作成一个对象,这就是面向对象的魅力,一切有赖于你对业务的分析进而转换为程序的对象
。
@Override
public Validator getValidator() {
return createValidator(
constraintValidatorManager.getDefaultConstraintValidatorFactory(),
valueExtractorManager,
validatorFactoryScopedContext,
methodValidationConfiguration
);
}
完成上面的数据组装成对象后,在ValidatorImpl以下方法对参数进行一个一个验证,并将违反约束的错误消息放到ConstraintViolation集合中,最终错误消息在页面显示。
private <T> void validateParametersForSingleGroup()
//974行 2. validate parameter constraints
for ( int i = 0; i < parameterValues.length; i++ ) {
//......此处省略了它的详细实现
//这里的for体现责任链一个个检查约束是否正确
validateMetaConstraints(.....)
}
private void validateMetaConstraints(ValidationContext<?> validationContext, ValueContext<?, Object> valueContext, Object parent,
Iterable<MetaConstraint<?>> constraints) {
for ( MetaConstraint<?> metaConstraint : constraints ) {
validateMetaConstraint( validationContext, valueContext, parent, metaConstraint );
if ( shouldFailFast( validationContext ) ) {
break;
}
}
}
从上面的这段主要验证代码中,我们可以学到什么?原来责任链是可以用List或数组把验证对象集合起来再进行检查的。
对于spring框架中的validator,它并不亲自实现所有的验证检查,而是采用适配器模式来调用hibernate-validator的实现
org.springframework.boot.context.properties.bind.validation.ValidationBindHandler
private void validateAndPush(ConfigurationPropertyName name, Object target, Class<?> type) {
ValidationResult result = null;
//这里又体现了责任链
for (Validator validator : this.validators) {
if (validator.supports(type)) {
result = (result != null) ? result : new ValidationResult(name, target);
validator.validate(target, result);
}
}
if (result != null && result.hasErrors()) {
this.exceptions.push(new BindValidationException(result.getValidationErrors()));
}
}
如何运用责任链模式
先看一段老破旧代码
if (!mer_no.equals(order.getMer_no())) {
log.info(mer_no.concat("==>02*****验证失败"));
throw new FaultException("02", "****验证失败");
}
if (!StringUtils.isBlank(order.getMobile())) {
TelephoneValidator validator = new TelephoneValidator(order.getMobile());
if (!validator.validate()) {
log.info(mer_no.concat("==>07:手机号错 input:").concat(order.getMobile()));
throw new FaultException("07", "手机号错");
}
} else {
log.info(mer_no.concat("==>07:手机号错 input:").concat(""));
throw new FaultException("07", "手机号错");
}
if (StringUtils.isBlank(order.getMer_oid())) {
log.info(mer_no.concat("==>06:订单号为空"));
throw new FaultException("06", "订单号为空");
}
if (order.getTk().size() == 0) {
log.info(mer_no.concat("==>04:门票信息为空"));
throw new FaultException("04", "门票信息为空");
}
// 是否在预售天数内
AgentBasic agentbasic = agentManager.getAgentBasicDAO().findById(mer_no);
String schemaCode = agentbasic.getSchemaCode();
int day = agentbasic.getTkTkBeforeDays();
Date d1 = new Date();
Date dayavailable = DateUtil.dateAdd(d1, day);
Date daymer = order.getValidDate();
long diff = dayavailable.getTime() - daymer.getTime();
long days = diff / (1000 * 60 * 60 * 24);
if (days <= 0) {
log.error(mer_no.concat("超出预售天数"));
throw new FaultException("04", "购票张数错、场次错、选择的日期不能销售该票");
}
// **********门票下午3点半以后不能售卖****************
Calendar rightnow = Calendar.getInstance();
int hour = rightnow.get(Calendar.HOUR_OF_DAY);
int minute = rightnow.get(Calendar.MINUTE);
String nowdate = DateUtil.dateToString(rightnow.getTime(), "yyyy-MM-dd");
String merdate = DateUtil.dateToString(date, "yyyy-MM-dd");
try {
/** 判断查询条件的开始时间是否比当前时间更前,这个为非法 */
if (sdf.parse(merdate).before(sdf.parse(nowdate))) {
log.error("代理商:"+mer_no+",时间已过期,现在:" + nowdate + ",查询时间:" + merdate);
st.setDate(newdate);
st.setResult("03");
return st;
}
/** 查询当天的,但是时间已过了下午3点半 */
if (nowdate.equals(merdate) && (hour >= 15 && minute >= 30)) {
log.info("商户:"+mer_no+", 下午3点半以后停止售票");
st.setDate(newdate);
st.setResult("03");
return st;
}
} catch (Exception e) {
log.error(e);
}
感觉如何?
1、if很多
2、有简单的,也有复杂的验证检查
3、同样的验证过程不利于复用,手机号,日期,预售天数的验证在其它方法中多次出现。
4、老破旧代码有时需要添加新功能,新程序判断,这时就很棘手。不利于调试
这种老代码又不能全盘采用成熟的新技术(相对于老代码),所以有时候我们就要从别的框架中学习他们的思想了。在这里,我们可以参考hibernate-validator或责任链模式将上面的验证步骤转化为一个个验证对象。
老破旧转化
我们分析这段代码,至少有三大块要做验证
1、简单的验证
2、预售天数的验证
3、某某时间点后不能售票
所以我们设计一个验证器工厂类ValidatorFactory,一个抽象验证器类AbstractValidator,三个验证器子类SimpleValidator–简单验证类,TimeSectionValidator–时段验证类,DateRangeValidator–预售日期验证类。其结构如下
工厂类产生三个子验证器,并把它们放到validators列表中,客户端程序调用factory.validator()方法执行validators列表中的验证器并处理错误消息。
主要代码如下
public class ValidatorFactory {
private static List<AbstractValidator> validators = new LinkedList<AbstractValidator>();
static {
validators.add(new SimpleValidator());
validators.add(new TimeSectionValidator());
validators.add(new DateRangeValidator());
}
private String message = null;
public boolean validator() {
boolean pass = true;
StringBuffer sb = new StringBuffer();
for (AbstractValidator abstractValidator : validators) {
if(!abstractValidator.validate()) {
pass = false;
sb.append(abstractValidator.getMessage()).append("|");
}
}
message = sb.toString();
return pass;
}
public String getMessage() {
return this.message;
}
}
抽象验证器类
public abstract class AbstractValidator {
public abstract boolean validator();
}
三个子类的代码将上面三段代码分别分配给子类的实现方法(其他两个子类的就不贴了)如下:
public class DateRangeValidator extends AbstractValidator {
public boolean validator(){
// **********门票下午3点半以后不能售卖****************
Calendar rightnow = Calendar.getInstance();
int hour = rightnow.get(Calendar.HOUR_OF_DAY);
int minute = rightnow.get(Calendar.MINUTE);
String nowdate = DateUtil.dateToString(rightnow.getTime(), "yyyy-MM-dd");
String merdate = DateUtil.dateToString(date, "yyyy-MM-dd");
try {
/** 判断查询条件的开始时间是否比当前时间更前,这个为非法 */
if (sdf.parse(merdate).before(sdf.parse(nowdate))) {
log.error("代理商:"+mer_no+",时间已过期,现在:" + nowdate + ",查询时间:" + merdate);
st.setDate(newdate);
st.setResult("03");
return st;
}
/** 查询当天的,但是时间已过了下午3点半 */
if (nowdate.equals(merdate) && (hour >= 15 && minute >= 30)) {
log.info("商户:"+mer_no+", 下午3点半以后停止售票");
st.setDate(newdate);
st.setResult("03");
return st;
}
} catch (Exception e) {
log.error(e);
}
}
}
最初的老破旧代码段就可以这样用
ValidatorFactory validatorFactory = new ValidatorFactory();
if(!validatorFactory.validator()){
throw new FaultException(“04”, validatorFactory.getMessage());
}
回顾、总结
1、本章节介绍了责任链模式的概念
2、粗略理解hibernate-validator的思想
3、偷用hibernate-validator的思想来解决实际工作中的问题
4、代码中验证逻辑很多的地方考虑使用责任链模式吧