首页 > 编程语言 >Java设计模式--装饰器模式

Java设计模式--装饰器模式

时间:2023-08-02 11:14:58浏览次数:37  
标签:Java -- equals 加班 overTime 休息日 规则 new 设计模式

Java设计模式--装饰器模式

一、问题背景

在项目场景中,有这样一个需求,需要对录入的加班进行规则校验 ,包括但不限于,对加班的录入时间进行检查,对录入的加班类型进行检查,对加班日期的班次进行对比检查,对潜入系统的时长进行对比检查等等。具体来说,就是对一条加班记录,进行多种规则的检查,判断加班录入的合理性。

image

二、项目实现

初代版本实现,完全遵循面对过程的编程思路,遍历记录后逐一进行规则校验。这样的实现方式最为基本,业务层代码和控制层代码在一起,对理解业务规则和代码执行顺序易于理解。但是缺点非常明显:

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("系统核实当天非节假日,请核实确认或联系管理员确认!!!");
        }
    }

}

基于上述组件创建后,新的实现如下:

  1. 首先是生成具体的对象,也就是加班记录
  2. 讲生成的加班记录先赋给recordBaseCheck,在将recordBaseCheck赋给recordMatchFieldCheck,然后依次赋给下一个规则,直至最后一个规则;
  3. 最后规则执行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); //如果没有报错的话,会把加班记录加入到待入库的集合中。
}

四、总结思考

在目前的项目中,除了加班的会存在规则验证外,我发现在权限验证,后期的调休等等场景中,都会存在针对一条对象进行多规则校验的情况,使用的场景还是很多的。装饰器模式是能很好的解决规则叠加维护的问题,但是隐隐觉得这个模式的运用也存在问题:

  1. 在springboot项目中,装饰器的规则类要不要交给spring容器去管理,如果交给spring容器去管理,因为容器对象的实例就一个,如果出现高并发的场景,会有多条记录去调用规则的实例,如果一条加班的规则还没有验证完,另一条记录就进来了,是会存在脏数据,导致校验出错的情况。目前我的解决方式是用synchronized进行同步锁。
  2. 接着上述场景,如果不交给spring容器管理,自己去new,那就无法使用spring的精髓,无法快速注入数据库或者redis的实例。

愈发觉得,在编程领域,功能的实现不是最难的,多功能大系统之间的融合才是最难的,怎么搭建好底层架构,做好铺垫非常重要,这个也算是约定大于配置大于编程的体现之一吧。

标签:Java,--,equals,加班,overTime,休息日,规则,new,设计模式
From: https://www.cnblogs.com/shutong1228/p/17600052.html

相关文章

  • 5类图和对象图
    类图是描述一组类、接口、写作和它们之间的关系对象图是描述一组对象和它们之间的关系。类:有类名、方法名、属性名有多重度:就是类这几间几对几的关系关系:依赖:a调用了b的方法,a依赖b,b变则a变  a- - ------>b泛化:特殊(子类)---->一般关系(父类)   ......
  • nnUNet 使用方法
    首先明确分割任务。其次明确研究方法和步骤。再做好前期准备,如数据集的采集、标注以及其中的训练集/测试集划分。其中的参考链接: (四:2020.07.28)nnUNet最舒服的训练教程(让我的奶奶也会用nnUNet(上))(21.04.20更新)_nnuet制定计划_花卷汤圆的博客-CSDN博客 最后,如果你需要具体的评......
  • Redis 发生高延迟时
    Redis是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快很多。但是Redis也会发生延迟时,这是就需要我们对其产生原因有深刻的了解,以便于快速排查问题,解决Redis的延迟问题一条命令执行过程在本文场景下,延迟(latency)是指从客户端发送命......
  • FPGA学习笔记 Label: Research
    [Synth8-9486]formalport'addr'hasnoactualordefaultvalue[D:/FPGA/TEST_CARD_HIT/top.vhd:492]有端口没有连接,在top文件中把端口加上 [Opt31-67]Problem:ALUT2cellinthedesignismissingaconnectiononinputpinI1,whichisusedbytheLUTequat......
  • 禁止别人调试自己的前端页面代码
    ......
  • 火山引擎ByteHouse:云原生数据库如何提升MySQL兼容性?
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群当前各类软件层出不穷,单独某一款软件往往难以满足企业应用需求,一般都需要与各类软件组合使用,这时软件生态兼容性就显得格外重要。作为关系数据库管理系统的代表之一,MySQL支持大多数操作......
  • mysql ssl
    【1】SSL简介(1.1)SSL简介安全一直是不可不重视的问题。目前MySQL这方面应大方向上技术手段都具备。如:网络链接,权限控制,key秘钥认证,数据加密脱敏等方式。综合考虑,虽然很多环境无法所有这些安全策略全部应用上,但在可控范围内尽量做到一定的防范实施。其中SSL是属于加密连接方式,......
  • 牛客周赛 Round 5
    牛客周赛Round5A-游游的字母变换_牛客周赛Round5(nowcoder.com)#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;signedmain(){ ios::sync_with_stdio(false); cin.tie(nullptr); strings;cin>>s;for(inti=0;i<s.size......
  • 使用python写ros publisher和subscriber
    publisher#!/usr/bin/envpython#licenseremovedforbrevityimportrospyfromstd_msgs.msgimportStringdeftalker():pub=rospy.Publisher('chatter',String,queue_size=10)rospy.init_node('talker',anonymous=True)ra......
  • EBS:供应商详细扩展字段属性设置
     -- EGO高级产品目录--N:供应商管理员>>供应商主页>>管理。--定义扩展字段属性,例如:DATABASE_COLUMN='C_EXT_ATTR7':企业性质SELECT*FROMAPPS.EGO_ATTRS_VWHEREATTR_GROUP_TYPE='POS_SUPP_PROFMGMT_GROUP'----N:供应商管理员>>供应商主页>>管理。SELECT*FRO......