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

  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;
      
          @Override
          public 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);
          }
      
          @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());
          }
      
      }
      
  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;
      
          @Override
          public <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)的深入理解:强制转换 vs 泛型

            你提出的这个问题非常好,确实是理解LSP的一个关键点。让我用一个更具体的例子来解释为什么行为被破坏了,以及和使用泛型有什么区别。

            生活化类比:通用插座 vs 特定插座

            想象一下:

            • 父类就像一个通用电源插座,承诺可以接受任何标准插头(两脚或三脚)

            • 子类就像一個特定插座,但外观和通用插座一样

            违反LSP的情况(强制转换):

            java
            // 这是一个"通用插座"(父类)
            class UniversalSocket {
                public void plugIn(Object plug) { // 承诺接受任何插头
                    System.out.println("通用插座正在供电");
                }
            }
            
            // 这是一个"特定插座"(子类),但外观和通用插座一样
            class TypeASocket extends UniversalSocket {
                @Override
                public void plugIn(Object plug) {
                    // 但实际上它只接受TypeA插头
                    TypeAPlug typeAPlug = (TypeAPlug) plug; // 强制转换!
                    System.out.println("TypeA插座正在供电");
                }
            }
            
            // 测试
            public class Test {
                public static void main(String[] args) {
                    UniversalSocket socket = new TypeASocket(); // 里氏替换:用子类代替父类
                    
                    // 根据父类的约定,这里应该可以插入任何插头
                    socket.plugIn(new TypeBPlug()); // 但运行时这里会爆炸!ClassCastException
                }
            }

            符合LSP的情况(使用泛型):

            java
            // 通用插座,但明确声明自己支持的类型
            class UniversalSocket<T extends Plug> {
                public void plugIn(T plug) { // 明确声明接受的插头类型
                    System.out.println("通用插座正在供电");
                }
            }
            
            // 特定插座,继承时明确指定类型
            class TypeASocket extends UniversalSocket<TypeAPlug> {
                @Override
                public void plugIn(TypeAPlug plug) { // 现在参数类型匹配了
                    System.out.println("TypeA插座正在供电");
                }
            }
            
            // 测试
            public class Test {
                public static void main(String[] args) {
                    UniversalSocket<TypeAPlug> socket = new TypeASocket(); // 类型安全
                    
                    socket.plugIn(new TypeAPlug()); // 编译通过,运行正常
                    // socket.plugIn(new TypeBPlug()); // 这行根本编译不过!编译器就报错了
                }
            }

            回到你的代码案例

            违反LSP的版本(强制转换):

            java
            // 父类:承诺接受任何FlowApprovalPageCondition
            class FlowHandlerGatewayImpl {
                protected void pageQueryWrapperExpand(LambdaQueryWrapper wrapper, 
                                                     FlowApprovalPageCondition condition) {
                    // 空实现,等待子类扩展
                }
            }
            
            // 子类:违反LSP
            class SimulationLoginGatewayImpl extends FlowHandlerGatewayImpl {
                @Override
                protected void pageQueryWrapperExpand(LambdaQueryWrapper wrapper, 
                                                     FlowApprovalPageCondition condition) {
                    // 破坏约定!实际上只接受SimulationLoginPageCondition
                    SimulationLoginPageCondition simCondition = (SimulationLoginPageCondition) condition;
                    
                    // 使用simCondition的特有方法...
                }
            }
            
            // 使用场景
            FlowHandlerGatewayImpl handler = new SimulationLoginGatewayImpl();
            handler.pageQueryWrapperExpand(wrapper, new OtherPageCondition()); // 运行时爆炸!

            符合LSP的版本(泛型):

            java
            // 父类:使用泛型明确约束
            class FlowHandlerGatewayImpl<C extends FlowApprovalPageCondition> {
                protected void pageQueryWrapperExpand(LambdaQueryWrapper wrapper, C condition) {
                    // 空实现
                }
            }
            
            // 子类:符合LSP
            class SimulationLoginGatewayImpl extends FlowHandlerGatewayImpl<SimulationLoginPageCondition> {
                @Override
                protected void pageQueryWrapperExpand(LambdaQueryWrapper wrapper, 
                                                     SimulationLoginPageCondition condition) {
                    // 现在安全了,参数类型明确匹配
                    // 直接使用condition的特有方法...
                }
            }
            
            // 使用场景
            FlowHandlerGatewayImpl<SimulationLoginPageCondition> handler = 
                new SimulationLoginGatewayImpl();
            handler.pageQueryWrapperExpand(wrapper, new SimulationLoginPageCondition()); // 安全
            // handler.pageQueryWrapperExpand(wrapper, new OtherPageCondition()); // 编译错误!

            关键区别和后果

             
            方面强制转换(违反LSP)泛型(符合LSP)
            错误发现时间 运行时才发现错误 编译时就能发现错误
            错误类型 ClassCastException(运行时异常) 编译错误
            代码安全性 低,容易产生隐藏bug 高,类型安全
            可维护性 差,需要阅读实现才知道限制 好,接口明确声明了限制
            替换透明度 不透明,子类有隐藏要求 透明,子类完全符合父类约定
            扩展性 差,添加新类型需要修改多处 好,添加新类型只需新增代码

            严重后果示例

            假设你的系统中有多种流程类型:

            java
            // 模拟登录流程
            SimulationLoginGatewayImpl simulationHandler = new SimulationLoginGatewayImpl();
            
            // 数据导出流程  
            DataExportGatewayImpl exportHandler = new DataExportGatewayImpl();
            
            // 一个通用的流程处理器(违反LSP的情况)
            List<FlowHandlerGatewayImpl> allHandlers = Arrays.asList(simulationHandler, exportHandler);
            
            // 处理所有流程
            for (FlowHandlerGatewayImpl handler : allHandlers) {
                // 这里传入的参数可能是任何FlowApprovalPageCondition子类
                handler.pageQueryWrapperExpand(wrapper, getRandomCondition()); // 俄罗斯轮盘赌!
                // 不知道哪个handler会在什么时候爆炸
            }

            这种代码就像一颗定时炸弹,你不知道它什么时候会爆炸,也不知道爆炸时会影响多少用户。

            总结

            里氏替换原则的核心思想是:子类应该能够透明地替换父类,而使用者不需要知道也不需要关心这种替换。

            • 强制转换破坏了这种透明性,子类有隐藏的、更严格的要求

            • 泛型通过类型参数明确声明了这种要求,保持了透明性

            关键区别在于:一个是运行时才发现问题(太晚了),一个是编译时就发现问题(正是我们想要的)。

            遵守LSP可以让你的系统更加健壮、可靠,而违反LSP则会在系统中埋下难以发现的定时炸弹。这就是为什么我们要使用泛型而不是强制转换的原因。

              

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

      • 使用泛型来保证类型安全,从而遵守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> {
            @Override
            protected 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,最终获得了极高的扩展性。

             

posted @ 2025-09-14 10:42  飘来荡去evo  阅读(8)  评论(0)    收藏  举报