Java设计模式--装饰器模式
一、问题背景
在项目场景中,有这样一个需求,需要对录入的加班进行规则校验 ,包括但不限于,对加班的录入时间进行检查,对录入的加班类型进行检查,对加班日期的班次进行对比检查,对潜入系统的时长进行对比检查等等。具体来说,就是对一条加班记录,进行多种规则的检查,判断加班录入的合理性。
二、项目实现
初代版本实现,完全遵循面对过程的编程思路,遍历记录后逐一进行规则校验。这样的实现方式最为基本,业务层代码和控制层代码在一起,对理解业务规则和代码执行顺序易于理解。但是缺点非常明显:
1、规则和规则之间属于紧耦合关系,不利于后期新规则的叠加和修改
2、单个函数的处理逻辑过于复杂且集中,代码复用性差,易造成代码冗余
//初代版本实现
List<String> empNoList = new ArrayList<>();
String statDate = FormatTemplate.DateTimeFormatter.parseDateTime(startTime).toString(FormatTemplate.DateFormatter);
//获取工号
if (empNo.contains(FormatTemplate.SPLITFLAG)) {
empNoList.addAll(Arrays.asList(empNo.split(FormatTemplate.SPLITFLAG)));
} else {
empNoList.add(empNo);
}
//检查录入的加班人员是否在人员池
List<String> poolEmpNo = poolService.list().stream().map(item -> item.getYgEmpNo()).collect(Collectors.toList());
boolean containsAll = poolEmpNo.containsAll(empNoList);
if (!containsAll) {
return new JsonReturn(0, "存在录入非人员池的工号,请联系管理员核实!!!", null);
}
//加班时长
DateTime end = FormatTemplate.DateTimeFormatter.parseDateTime(endTime);
DateTime start = FormatTemplate.DateTimeFormatter.parseDateTime(startTime);
if(end.isBefore(start)){
return new JsonReturn(0, "加班结束时间早于加班开始时间!!!", null);
}
int hours = Hours.hoursBetween(start, end).getHours();
if(hours>=10){
return new JsonReturn(0, "加班时间超过10小时,数据异常!!!", null);
}
Double i = Minutes.minutesBetween(start, end).getMinutes() / 60.0;
//工作日不能选休息日
if(addWorkType.equals("工作日加班") && emergencyType.equals("休息日加班")){
return new JsonReturn(0, "工作日加班,加班时段,不能选择休息日加班!!!", null);
}
//休息日加班只能选休息日加班
if(addWorkType.equals("休息日加班") && !emergencyType.equals("休息日加班")){
return new JsonReturn(0, "休息日加班,加班时段,只能选择休息日加班!!!", null);
}
//专业类型
if(addWorkType.equals("二级加班") || addWorkType.equals("主动应急")){
if(!checkType.equals("运营加班")){
return new JsonReturn(0, "二级加班和主动应急只能是运营加班!!!", null);
}
}
//判断是否是节假日
LambdaQueryWrapper<DataHoliday> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DataHoliday::getHoliday,"3").eq(DataHoliday::getStateDate,statDate);
DataHoliday one = holidayService.getOne(queryWrapper);
if(Objects.isNull(one) && addWorkType.equals("节假日加班")){
return new JsonReturn(0, "系统核实当天非节假日,请核实确认或联系管理员确认!!!", null);
}
List<MangetOrgPerson> people = personService.list();
//匹配班次
for (String s : empNoList) {
List<MangetOrgPerson> orgPeople = people.stream().filter(item -> item.getUserNo().equals(s)).collect(Collectors.toList());
if(orgPeople.size()>0){
MangetOrgPerson orgPerson = orgPeople.get(0);
String status = orgPerson.getPersonStatus();
if(Objects.nonNull(status) && status.equals("哺乳")){
continue;
}
}
HashMap<String, Object> hashMap = overTimeService.checkClassName(s, startTime, endTime, emergencyType);
Boolean flag = (Boolean) hashMap.get("flag");
String msg = (String) hashMap.get("msg");
if (!flag) {
return new JsonReturn(0,msg, null);
}
}
//是否存在时间重复
for (String s : empNoList) {
Boolean isRepetition = overTimeService.checkIsRepetition(s, startTime, endTime);
if (isRepetition) {
return new JsonReturn(0, s+"加班时间存在重叠,请检查后重新录入!!!!", null);
}
}
三、装饰器模式
装饰器模式是一种结构型设计模式,允许通过动态地将新功能添加到现有对象上,来扩展其行为或修改其外观,同时不改变其原始类的结构。
在具体问题里,加班的记录就是现有的对象,行为和外观就是为了验证这个加班记录的规则,可以通过装饰器模式动态的去扩展这些规则。在通俗点说就好像是芭比娃娃的游戏,芭比娃娃是现有独享,行为和外观是芭比娃娃的衣服。
装饰器模式的实现主要有四个部分
//定义抽象组件类 CheckAddWork,这里主要是利用面向对象多态的特性,后面的具体的规则都会实现这个接口
public interface CheckAddWork {
void check() throws RuntimeException;
}
//具体的组件类,组件实现CheckAddWork接口,具体用提是创造具体的对象,这里还执行了一些最基本的逻辑验证
public class RecordBaseCheck implements CheckAddWork {
private DataOverTime addRecord;
@Override
public void check() throws RuntimeException {
//todo
log.info("基础检查");
DateTime end = FormatTemplate.DateTimeFormatter.parseDateTime(this.addRecord.getAddTimeStart());
DateTime start = FormatTemplate.DateTimeFormatter.parseDateTime(this.addRecord.getAddTimeEnd());
Double addHours = Minutes.minutesBetween(start, end).getMinutes() / 60.0;
if(addHours >= 10){
throw new RuntimeException(this.addRecord.getEmpNo()+"加班时间超过10小时,数据异常,请核查!!!");
}
if(end.isEqual(start)){
throw new RuntimeException(this.addRecord.getEmpNo()+"加班开始时间不能等于加班结束时间,数据异常,请核查!!!");
}
if(end.isBefore(start)){
throw new RuntimeException(this.addRecord.getEmpNo()+"加班结束时间早于加班开始时间,数据异常,请核查!!!");
}
}
}
//装饰器抽象类,这个类实现了CheckAddWork接口,后面的规则都会继承这个抽象类
public abstract class CheckAddWorkDecorator implements CheckAddWork{
protected CheckAddWork checkAddWork;
@Override
public void check() throws RuntimeException {
checkAddWork.check();
}
}
//具体规则(可以有很多规则)
public class RecordTypeErrorCheck extends CheckAddWorkDecorator {
@Autowired
private RecordBaseCheck recordBaseCheck;
@Autowired
private DataHolidayService holidayService;
@Override
public void check() throws RuntimeException {
super.check(); //关键
// todo
log.info("检查加班类型的逻辑错误!!!");
DataOverTime addRecord = recordBaseCheck.getAddRecord();
String addWorkType = addRecord.getAddWorkType();
String emergencyType = addRecord.getEmergencyType();
String workSource = addRecord.getAddWorkSource();
if(addWorkType.equals("工作日加班") && emergencyType.equals("休息日加班")){
throw new RuntimeException("工作日加班,加班时段,不能选择休息日加班!!!");
}
if(addWorkType.equals("休息日加班") && !emergencyType.equals("休息日加班")){
throw new RuntimeException("休息日加班,加班时段,只能选择休息日加班!!!");
}
if(addWorkType.equals("二级加班") || addWorkType.equals("主动应急")){
if(!workSource.equals("运营加班")){
throw new RuntimeException("二级加班和主动应急只能是运营加班!!!");
}
}
String statDate = FormatTemplate.DateTimeFormatter.parseDateTime(addRecord.getAddTimeStart()).toString(FormatTemplate.DateFormatter);
LambdaQueryWrapper<DataHoliday> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DataHoliday::getHoliday,"3").eq(DataHoliday::getStateDate,statDate);
DataHoliday one = holidayService.getOne(queryWrapper);
if(Objects.isNull(one) && addWorkType.equals("节假日加班")){
throw new RuntimeException("系统核实当天非节假日,请核实确认或联系管理员确认!!!");
}
}
}
基于上述组件创建后,新的实现如下:
- 首先是生成具体的对象,也就是加班记录
- 讲生成的加班记录先赋给recordBaseCheck,在将recordBaseCheck赋给recordMatchFieldCheck,然后依次赋给下一个规则,直至最后一个规则;
- 最后规则执行check()函数,因为每个check函数都会先执行super.check(),所以会回滚到最初,先是从recordBaseCheck的check()开始执行。(有一点俄罗斯套娃的感觉)
for (String s : empNoList) {
DataOverTime overTime = new DataOverTime(); //创建现有对象
overTime.setHandler(user);
overTime.setAddDate(statDate);
overTime.setEmpNo(s);
overTime.setIsEmergency(isEmergency);
overTime.setAddWorkType(addWorkType);
overTime.setEmergencyType(emergencyType);
overTime.setAddWorkSource(checkType);
overTime.setAddTimeStart(startTime);
overTime.setAddTimeEnd(endTime);
overTime.setEmergencyTime(String.format("%.2f", addWorkTime));
overTime.setCreateTime(DateTime.now().toString(FormatTemplate.DateTimeFormatter));
overTime.setReason(reason);
overTime.setIsCheck("未审核");
overTime.setDelFlag("否");
overTime.setIsTwoDay("否");
synchronized (this) {
recordBaseCheck.setAddRecord(overTime); //基础检查
recordMatchFieldCheck.setCheckAddWork(recordBaseCheck); //匹配字段
isTimeDuplicationCheck.setCheckAddWork(recordMatchFieldCheck); // 检查时间是否重复
recordTypeErrorCheck.setCheckAddWork(isTimeDuplicationCheck); //检查加班类型错误
recordClassErrorCheck.setCheckAddWork(recordTypeErrorCheck); //检查加班班次的错误
recordClassErrorCheck.check(); //执行检查程序
}
dataOverTimes.add(overTime); //如果没有报错的话,会把加班记录加入到待入库的集合中。
}
四、总结思考
在目前的项目中,除了加班的会存在规则验证外,我发现在权限验证,后期的调休等等场景中,都会存在针对一条对象进行多规则校验的情况,使用的场景还是很多的。装饰器模式是能很好的解决规则叠加维护的问题,但是隐隐觉得这个模式的运用也存在问题:
- 在springboot项目中,装饰器的规则类要不要交给spring容器去管理,如果交给spring容器去管理,因为容器对象的实例就一个,如果出现高并发的场景,会有多条记录去调用规则的实例,如果一条加班的规则还没有验证完,另一条记录就进来了,是会存在脏数据,导致校验出错的情况。目前我的解决方式是用synchronized进行同步锁。
- 接着上述场景,如果不交给spring容器管理,自己去new,那就无法使用spring的精髓,无法快速注入数据库或者redis的实例。
愈发觉得,在编程领域,功能的实现不是最难的,多功能大系统之间的融合才是最难的,怎么搭建好底层架构,做好铺垫非常重要,这个也算是约定大于配置大于编程的体现之一吧。
标签:Java,--,equals,加班,overTime,休息日,规则,new,设计模式 From: https://www.cnblogs.com/shutong1228/p/17600052.html