当前位置: 首页 > news >正文

子类不依赖泛型,重写父类方法,通过强制类型转换父类方法参数出现的问题。——— 一个例子引发的思考

  1. 使用泛型(推荐)

    1. public interface FlowHandlerGateway<P extends FlowApprovalPageCondition> {Page<FlowApprovalPage> pageQuery(P condition);
      }//父类
      @Slf4j
      @Component
      @RequiredArgsConstructor
      public class FlowHandlerGatewayImpl<P extends FlowApprovalPageCondition> implements FlowHandlerGateway<P>{private final FlowApprovalWrapper flowApprovalWrapper;private final InfraConverter converter;private final CommonAdapter commonAdapter;@Overridepublic Page<FlowApprovalPage> pageQuery(P condition) {Page<FlowApprovalPO> pageInfo = commonAdapter.toPage(condition);LambdaQueryWrapper<FlowApprovalPO> wrapper = Wrappers.<FlowApprovalPO>lambdaQuery().eq(StrUtil.isNotBlank(condition.getBizNo()), FlowApprovalPO::getBizNo, condition.getBizNo()).eq(StrUtil.isNotBlank(condition.getFlowType()), FlowApprovalPO::getFlowType, condition.getFlowType()).eq(ObjectUtil.isNotNull(condition.getInstanceId()), FlowApprovalPO::getInstanceId, condition.getInstanceId()).eq(StrUtil.isNotBlank(condition.getFlowStatus()), FlowApprovalPO::getFlowStatus, condition.getFlowStatus()).eq(ObjectUtil.isNotNull(condition.getApplyBy()), FlowApprovalPO::getApplyBy, condition.getApplyBy()).like(StrUtil.isNotBlank(condition.getApplyByName()), FlowApprovalPO::getApplyByName, condition.getApplyByName()).ge(ObjectUtil.isNotNull(condition.getApplyStartTime()), FlowApprovalPO::getCreatedAt, condition.getApplyStartTime()).le(ObjectUtil.isNotNull(condition.getApplyEndTime()), FlowApprovalPO::getCreatedAt, condition.getApplyEndTime());// Wrapper拓展方法pageQueryWrapperExpand(wrapper, condition);return converter.toFlowApprovalPage(flowApprovalWrapper.page(pageInfo, wrapper));}protected void pageQueryWrapperExpand(LambdaQueryWrapper<FlowApprovalPO> wrapper, P conditionPage){}
      }/*** 子类*/
      @Component
      @Slf4j
      public class SimulationLoginGatewayImpl extends FlowHandlerGatewayImpl<SimulationLoginPageCondition>{public SimulationLoginGatewayImpl(FlowApprovalWrapper flowApprovalWrapper, InfraConverter converter, CommonAdapter commonAdapter) {super(flowApprovalWrapper, converter, commonAdapter);}@Overrideprotected void pageQueryWrapperExpand(LambdaQueryWrapper<FlowApprovalPO> wrapper, SimulationLoginPageCondition condition){wrapper.apply(ObjectUtil.isNotNull(condition.getTenantId()),"biz_data->>'$.tenant_id' = {0}", condition.getTenantId()).apply(ObjectUtil.isNotNull(condition.getApplicationId()),"biz_data->>'$.application_id' = {0}", condition.getApplicationId()).apply(ObjectUtil.isNotNull(condition.getEnableStatus()),"biz_data->>'$.enable_status' = {0}", condition.getEnableStatus()).apply(ObjectUtil.isNotNull(condition.getEmail()),"biz_data->>'$.email' = {0}", condition.getEmail());}}
      
  2. 通过强制类型转换。其中SimulationLoginPageCondition实体继承FlowApprovalPageCondition实体,子类SimulationLoginPageCondition为什么不能使用多态特性,直接重写父类pageQueryWrapperExpand方法    


    1. // 父类
      @Slf4j @Component @RequiredArgsConstructor public class FlowHandlerGatewayImpl implements FlowHandlerGateway{private final FlowApprovalWrapper flowApprovalWrapper;private final InfraConverter converter;private final CommonAdapter commonAdapter;@Overridepublic <P extends FlowApprovalPageCondition> Page<FlowApprovalPage> pageQuery(P condition) {Page<FlowApprovalPO> pageInfo = commonAdapter.toPage(condition);LambdaQueryWrapper<FlowApprovalPO> wrapper = Wrappers.<FlowApprovalPO>lambdaQuery().eq(StrUtil.isNotBlank(condition.getBizNo()), FlowApprovalPO::getBizNo, condition.getBizNo()).eq(StrUtil.isNotBlank(condition.getFlowType()), FlowApprovalPO::getFlowType, condition.getFlowType()).eq(ObjectUtil.isNotNull(condition.getInstanceId()), FlowApprovalPO::getInstanceId, condition.getInstanceId()).eq(StrUtil.isNotBlank(condition.getFlowStatus()), FlowApprovalPO::getFlowStatus, condition.getFlowStatus()).eq(ObjectUtil.isNotNull(condition.getApplyBy()), FlowApprovalPO::getApplyBy, condition.getApplyBy()).like(StrUtil.isNotBlank(condition.getApplyByName()), FlowApprovalPO::getApplyByName, condition.getApplyByName()).ge(ObjectUtil.isNotNull(condition.getApplyStartTime()), FlowApprovalPO::getCreatedAt, condition.getApplyStartTime()).le(ObjectUtil.isNotNull(condition.getApplyEndTime()), FlowApprovalPO::getCreatedAt, condition.getApplyEndTime());// Wrapper拓展方法pageQueryWrapperExpand(wrapper, condition);return converter.toFlowApprovalPage(flowApprovalWrapper.page(pageInfo, wrapper));}protected void pageQueryWrapperExpand(LambdaQueryWrapper<FlowApprovalPO> wrapper, FlowApprovalPageCondition conditionPage){} }

      /**
      * 子类
      */
      @Component
      @Slf4j
      public class SimulationLoginGatewayImpl extends FlowHandlerGatewayImpl{

      public SimulationLoginGatewayImpl(FlowApprovalWrapper flowApprovalWrapper, InfraConverter converter, CommonAdapter commonAdapter) {
      super(flowApprovalWrapper, converter, commonAdapter);
      }

        // 出现报错,重写方法参数和父类不一样、违反了java规范
      @Override
      protected void pageQueryWrapperExpand(LambdaQueryWrapper<FlowApprovalPO> wrapper, SimulationLoginPageCondition condition){
      wrapper.apply(ObjectUtil.isNotNull(condition.getTenantId()),"biz_data->>'$.tenant_id' = {0}", condition.getTenantId())
      .apply(ObjectUtil.isNotNull(condition.getApplicationId()),"biz_data->>'$.application_id' = {0}", condition.getApplicationId())
      .apply(ObjectUtil.isNotNull(condition.getEnableStatus()),"biz_data->>'$.enable_status' = {0}", condition.getEnableStatus())
      .apply(ObjectUtil.isNotNull(condition.getEmail()),"biz_data->>'$.email' = {0}", condition.getEmail());
      }
      // 强制类型转换,子类中重写父类方法,也不推荐后面有原因,违反了开闭原则,下面有讲解
      @Override
      protected void pageQueryWrapperExpand(LambdaQueryWrapper<FlowApprovalPO> wrapper, FlowApprovalPageCondition condition) {// 强制类型转换SimulationLoginPageCondition simCondition = (SimulationLoginPageCondition) condition;// 使用转换后的条件wrapper.apply(ObjectUtil.isNotNull(simCondition.getTenantId()),"biz_data->>'$.tenant_id' = {0}", simCondition.getTenantId()).apply(ObjectUtil.isNotNull(simCondition.getApplicationId()),"biz_data->>'$.application_id' = {0}", simCondition.getApplicationId()).apply(ObjectUtil.isNotNull(simCondition.getEnableStatus()),"biz_data->>'$.enable_status' = {0}", simCondition.getEnableStatus()).apply(ObjectUtil.isNotNull(simCondition.getEmail()),"biz_data->>'$.email' = {0}", simCondition.getEmail());
      }


      } 
  3. 原理

    1. 1. 单一职责原则 (Single Responsibility Principle - SRP)

      核心思想:一个类应该只有一个引起它变化的原因。换句话说,一个类应该只负责一项职责。

      在Java中的理解:

      • 好处:类的职责越单一,它的内聚性就越高,就越容易被理解、维护和修改。修改一个功能不会意外影响到其他不相关的功能。

      • ** violation (违反)的例子**:如果一个类既负责数据库操作,又负责业务逻辑计算,还负责发送邮件,那它就违反了SRP。此时,修改数据库连接方式、业务算法或邮件服务器配置都会修改这个类,风险很高。

      结合你的代码:

      • FlowHandlerGatewayImpl 的职责非常明确:构建查询流程审批单的分页条件。它不关心具体的SQL执行(由FlowApprovalWrapper负责),也不关心PO到Domain的转换(由InfraConverter负责)。它通过依赖注入将其他职责委托给了专门的类,这很好地遵循了SRP。


      2. 开闭原则 (Open/Closed Principle - OCP)

      核心思想:软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭。

      在Java中的理解:

      • “对修改关闭”:意味着一个已经完成并测试通过的类的核心代码不应该再被修改。

      • “对扩展开放”:意味着当有新的需求时,你应该能够通过扩展这个类(如通过继承、组合、实现接口等方式)来添加新功能,而不是修改它。

      • 实现手段:抽象(接口、抽象类)和多态是实现OCP的关键。

      结合你的代码:

      • FlowHandlerGatewayImpl 的 pageQueryWrapperExpand 方法是一个空实现(钩子方法)。这本身就是为扩展留下的“窗口”。

      • 当需要为SimulationLoginPageCondition添加特殊的查询条件时,你没有修改 FlowHandlerGatewayImpl 的核心 pageQuery 方法,而是扩展了它,创建了SimulationLoginGatewayImpl子类并重写了pageQueryWrapperExpand方法。

      • 这正是对扩展开放,对修改关闭的完美体现。父类代码稳定,新功能通过子类扩展实现。


      3. 里氏替换原则 (Liskov Substitution Principle - LSP)

      核心思想:所有引用基类(父类)的地方必须能透明地使用其子类的对象,而程序的行为不会发生变化。

      在Java中的理解:

      • 子类可以扩展父类的功能,但不能改变父类原有的功能和行为约定(如方法签名、返回值、异常抛出等)。

      • 子类不应该比父类有更严格的前置条件(比如,父类方法参数是Integer,子类重写时却要求参数必须大于0,这就违反了LSP)。

      • ** violation (违反)的例子:这正是你之前遇到的问题。如果强行在子类中将FlowApprovalPageCondition参数转换为SimulationLoginPageCondition,那么该子类对象就无法透明替换父类对象。因为父类方法可以接受任何FlowApprovalPageCondition,而你的子类方法实际上只接受特定的子类型,传入其他类型会导致ClassCastException,行为被破坏了**。

      正确的做法(结合你的代码):

      • 使用泛型来保证类型安全,从而遵守LSP。

        java
        // 父类
        public class FlowHandlerGatewayImpl<C extends FlowApprovalPageCondition> {public Page<FlowApprovalPage> pageQuery(C condition) { ... }protected void pageQueryWrapperExpand(LambdaQueryWrapper<FlowApprovalPO> wrapper, C condition) {}
        }// 子类
        public class SimulationLoginGatewayImpl extends FlowHandlerGatewayImpl<SimulationLoginPageCondition> {@Overrideprotected void pageQueryWrapperExpand(LambdaQueryWrapper<FlowApprovalPO> wrapper, SimulationLoginPageCondition condition) {// 这里直接使用SimulationLoginPageCondition,无需强制转换,且类型绝对安全}
        }
      • 现在,任何期望使用FlowHandlerGatewayImpl<SimulationLoginPageCondition>的地方,都可以安全地用SimulationLoginGatewayImpl来替换,因为子类方法完全满足父类方法的契约(参数是SimulationLoginPageCondition,它是FlowApprovalPageCondition的子类),行为一致且不会出错。


      4. 扩展性 (Extensibility)

      核心思想:软件系统能够容易地适应新需求、添加新功能,而所需的工作量和成本最低,且对现有系统的影响最小。

      在Java中的理解:

      • 扩展性不是某个单一原则,而是良好应用上述所有原则(SRP, OCP, LSP)以及依赖倒置、接口隔离等原则后的自然结果。

      • 一个高扩展性的系统,其结构是松耦合的,通过抽象和接口定义契约,使得添加新模块就像“插拔组件”一样简单。

      结合你的代码:

      • 你目前的设计(尤其是使用泛型重构后)具有很高的扩展性。

      • 如何添加一个新的流程类型(如DataExportPageCondition)?

        1. 创建新的条件类:DataExportPageCondition extends FlowApprovalPageCondition

        2. 创建新的网关子类:DataExportGatewayImpl extends FlowHandlerGatewayImpl<DataExportPageCondition>

        3. 重写扩展方法:在子类中重写pageQueryWrapperExpand,添加数据导出特有的查询逻辑。

      • 你做到了什么?

        • 没有修改任何现有的、稳定的父类代码(FlowHandlerGatewayImpl) -> 遵循OCP。

        • 新功能在独立的、职责单一的新类中完成 -> 遵循SRP。

        • 新的子类可以完全替换父类,行为一致 -> 遵循LSP。

        • 整个过程的代价极小,风险极低,因为只是添加新代码而不是修改老代码。

      总结

       
      原则核心思想在示例中的体现(良好设计后)
      单一职责 (SRP) 一个类只干一件事 FlowHandlerGatewayImpl只负责构建查询Wrapper,其他职责外包。
      开闭原则 (OCP) 对扩展开放,对修改关闭 通过泛型和可重写的钩子方法,允许无限扩展新的查询条件类型,而无需修改基类。
      里氏替换 (LSP) 子类必须能透明替换父类 使用泛型后,子类SimulationLoginGatewayImpl可以安全替换FlowHandlerGatewayImpl<SimulationLoginPageCondition>
      扩展性 容易添加新功能 添加新流程类型非常简单、快速且安全,是应用上述原则后的必然结果。

      这些原则共同作用,指导我们构建出健壮、灵活、易于维护和扩展的Java应用程序。你代码中最初遇到的问题,正是因为没有很好地满足LSP,进而影响了扩展的优雅性。通过引入泛型,你同时完美地践行了SRP、OCP和LSP,最终获得了极高的扩展性。

             

http://www.wxhsa.cn/company.asp?id=3437

相关文章:

  • WebStorm代码一键美化
  • 3分钟搞定Vue组件库
  • Golang中设置HTTP请求代理的策略
  • [开源免费] iGTTS(Gemini TTS) 文本转语音(TTS)的命令行工具。
  • 结合Spring和MyBatis实现DAO层操作综述
  • 202205_CHIMA_follow
  • Lua脚本协助Redis分布式锁实现命令的原子性
  • 快读快写 学习笔记
  • Ubuntu 安装 CLion
  • AI编程实战
  • 25/9/13(补)
  • 面向对象编程(OOP)的原则
  • 【龙智Atlassian插件】Confluence周报插件上线AI智能总结,一键生成专业报告 - 实践
  • 数字化(管理)系统的工具化思考
  • 详细介绍:传统神经网络实现-----手写数字识别(MNIST)项目
  • C#语言中使用using关键字
  • 中育新版本OSS Token获取API分析
  • 25/9/12(补,上一篇是9/11的)
  • 动态编译 vs. 静态编译,容器时代那个更有优势?
  • 实用指南:操作系统类型全解析:从批处理到嵌入式
  • 【C++ 类和对象・高阶深化(下)】再探构造函数(含初始化列表),吃透 static 成员、友元、内部类及对象拷贝编译器优化 - 指南
  • VSCode 运行 C/C++ 程序
  • 3 字节
  • Springcloud Alibaba(一)
  • 111111111
  • 202204_DASCTF_SimpleFlow
  • 使用 Winscope 跟踪窗口转换
  • 25/9/12(补)
  • 深入解析:“纳米总管”——Arduino Nano 的趣味生活
  • 洛谷题目难度系统优化