第二次Blog作业

面向对象程序设计题目集8-9总结性Blog

一、前言

题目集8和题目集9聚焦于“航空货运管理系统”的设计与实现,旨在考查面向对象编程中的类封装、继承、多态、抽象类与接口的应用,以及复杂业务逻辑的结构化设计能力。两次题目集的题量均为1题,但题目集9在业务逻辑复杂度上略有提升,整体难度适中,侧重检验代码的规范性、可维护性和功能完整性。

(一)知识点覆盖

  1. 类的封装与设计:要求将业务实体(如客户、货物、航班、订单等)封装为独立类,通过私有属性和公共方法实现数据隐藏与访问控制。
  2. 继承与多态:利用抽象类CustomerGoods定义公共接口,通过IndividualCustomer/CorporateCustomerNormalGoods/DangerousGoods/ExpediteGoods实现多态,体现“is-a”关系。
  3. 抽象类与接口CustomerGoods作为抽象类,强制子类实现关键方法(如getDiscount()getRate()),确保代码结构的规范性。
  4. 业务逻辑实现:涉及货物计费重量计算、运费计算、订单信息打印、超载判断等复杂逻辑,需综合运用条件判断、循环结构和方法调用。
  5. 输入输出处理:通过Scanner实现控制台输入,要求代码具备良好的输入容错能力和格式化输出能力。

(二)题量与难度

  • 题目集8:实现基础的航空货运系统框架,包括客户、货物、订单的基本类设计,重点考查类的继承关系和抽象方法的实现。难度适中,代码量约200行。
  • 题目集9:在题目集8的基础上增加支付方式(微信、支付宝、现金)的多态实现,并优化订单信息打印逻辑,要求处理更复杂的输入流程和业务规则。难度略有提升,代码量约350行。

二、设计与分析:航空货运管理系统源码解析

(一)SourceMonitor代码质量分析

通过SourceMonitor工具对两次题目集的代码进行静态分析,生成的报表数据可反映代码的结构质量和可维护性。以下是关键指标对比:

1. 题目集8分析

参数 说明
Lines 122 代码总行数较少,结构较简洁,但可能存在逻辑集中于少数方法的问题。
Statements 80 有效语句数占比约65.6%,代码冗余度较低。
Percent Branch Statements 8.7% 分支语句占比低,逻辑结构简单,主要集中于getRate()方法的条件判断。
Method Call Statements 46 方法调用频繁,体现了面向对象编程中通过方法协作实现业务逻辑的特点。
Percent Lines with Comments 4.9% 严重不足,注释覆盖率极低,影响代码可读性。
Classes and Interfaces 2 仅包含CargoMain类,类设计过于集中,未体现业务模块的合理拆分。
Maximum Complexity 5 Cargo.getRate()方法复杂度最高,存在多层条件判断,可考虑重构。

分析

  • 优势:平均复杂度(2.17)和块深度(1.86)较低,代码结构较清晰。
  • 缺陷:注释率极低(4.9%),类数量不足,违反“单一职责原则”,导致Cargo类承担过多职责。

2. 题目集9分析

参数 说明
Lines 约700 代码量显著增加,业务逻辑更复杂。
Statements 约500 有效语句占比约71.4%,代码结构更紧凑。
Percent Branch Statements 15% 分支语句占比提升,主要源于支付方式的多态判断和订单逻辑的扩展。
Method Call Statements 200+ 方法调用复杂度显著上升,体现了类间协作的增强(如Order类调用GoodsCustomer的方法)。
Percent Lines with Comments 8% 注释率略有提升,但仍低于理想水平(建议20%+)。
Classes and Interfaces 12+ 新增PaymentMethod及其子类、FlightOrder等类,类设计更符合业务模块化需求。
Maximum Complexity 5 ExpediteGoods.getRate()Main.main()方法复杂度较高,需关注逻辑优化。

对比

  • 改进点:类数量合理增加,模块职责分离更清晰(如Order类负责订单处理,Goods子类负责计费逻辑);多态应用(支付方式、客户类型、货物类型)提升了代码扩展性。
  • 待优化点Main.main()方法复杂度为4,输入处理逻辑冗长,可拆分为独立的InputHandler类;部分子类(如WechatPayment)的方法过于简单,可考虑使用枚举简化。

(二)类图设计与分析

1. 题目集8类图

  • 问题
    • Cargo类同时承担客户、货物、订单的逻辑,违反“单一职责原则”,导致类复杂度高。
    • 缺乏抽象类设计,所有方法直接在具体类中实现,不利于后续扩展(如新增货物类型需修改Cargo类)。

2. 题目集9类图

  • 改进

    • 引入抽象类CustomerGoods,定义公共接口,子类(如IndividualCustomerNormalGoods)实现具体逻辑,符合“开闭原则”。
    • 新增PaymentMethod接口及其子类,通过多态实现支付方式的灵活扩展。
    • Order类聚合CustomerFlightGoods[]PaymentMethod,体现“组合复用原则”,逻辑分层更清晰。
  • 典型代码示例:多态的应用

    // 客户类型多态
    Customer customer;
    if (customerType.equals("Individual")) {
        customer = new IndividualCustomer(...);
    } else {
        customer = new CorporateCustomer(...);
    }
    
    // 货物类型多态
    switch (goodsType) {
        case "Normal":
            goodsList[i] = new NormalGoods(...);
            break;
        // 其他类型类似
    }
    
    // 支付方式多态
    switch (paymentMethodStr) {
        case "Wechat":
            paymentMethod = new WechatPayment();
            break;
        // 其他类型类似
    }
    

    分析:通过多态避免了大量if-else嵌套,提升了代码的可扩展性。例如,新增VIPCustomer时,只需创建子类并实现getDiscount()方法,无需修改Order类的计费逻辑。

(三)核心业务逻辑分析

1. 计费重量与运费计算

  • 规则
    计费重量 = max(实际重量, 体积重量),体积重量 = 长×宽×高÷6000
    运费 = 计费重量 × 费率 × 客户折扣
  • 代码实现
    // Goods抽象类
    public double getChargeableWeight() {
        return Math.max(weight, calculateVolumeWeight());
    }
    
    public double calculateShippingFee() {
        return getChargeableWeight() * getRate(); // 多态调用子类费率
    }
    
    // NormalGoods子类费率计算
    public double getRate() {
        double chargeableWeight = getChargeableWeight();
        if (chargeableWeight < 20) return 35;
        // 其他条件...
    }
    
    优点:通过抽象类统一接口,子类实现具体费率策略,符合“策略模式”思想,便于后续新增计费策略(如促销活动)。

2. 订单超载判断与信息打印

  • 逻辑
    isOverloaded()方法通过比较订单总重量与航班最大载重能力实现超载判断;printOrderInfo()方法根据判断结果输出不同信息,使用格式化输出确保可读性。
  • 代码示例
    public boolean isOverloaded() {
        return calculateTotalWeight() > flight.getMaxLoadCapacity();
    }
    
    public void printOrderInfo() {
        if (isOverloaded()) {
            // 输出超载信息
        } else {
            // 输出详细订单信息,使用printf格式化
            System.out.printf("订单总重量(kg):%.1f\n", calculateTotalWeight());
            System.out.printf("%s支付金额:%.1f\n", paymentMethod.getPaymentMethod(), total);
        }
    }
    
    优化点:格式化输出使用%.1f确保精度统一,但未处理输入为空的情况(如未填写地址),可能导致输出异常。

三、采坑心得:从代码错误到优化实践

(一)继承与抽象类的典型问题

1. 子类未实现抽象方法

  • 错误场景:题目集9中,首次创建CorporateCustomer类时未实现getDiscount()方法,导致编译报错。
    // 错误代码(缺少getDiscount()方法)
    class CorporateCustomer extends Customer {
        public CorporateCustomer(...) { super(...); }
    }
    
  • 原因分析:抽象类强制子类必须实现所有抽象方法,否则子类需声明为抽象类。
  • 解决方法:添加getDiscount()方法实现。
    public double getDiscount() { return 0.8; }
    

2. 子类构造方法未调用父类构造方法

  • 错误场景:创建NormalGoods实例时,编译器报错“无法找到合适的父类构造器”。
  • 原因分析:Java子类构造方法默认调用父类无参构造器,但若父类(如Goods)未提供无参构造器(仅存在有参构造器),需显式调用super(参数)
  • 解决方法:在子类构造方法中添加super(id, name, width, length, height, weight);

3. 错误实例化抽象类

  • 错误场景:尝试创建CustomerGoods的实例。
    Customer customer = new Customer(1, "..."); // 编译错误
    
  • 原因分析:抽象类不能直接实例化,只能通过子类对象引用父类变量(多态)。
  • 解决方法:通过子类创建对象,如new IndividualCustomer(...)

(二)类设计与职责分离问题

1. 上帝类(God Class)问题

  • 场景:题目集8中Cargo类承担客户、货物、订单的所有逻辑,代码行数超过200行(记得老师说不要超过20行来着,但是感觉有点困难),导致方法调用混乱(本来以为题目和之前的一样的难的,就想着写快点就偷懒了)。
  • 影响:违反“单一职责原则”,增加调试和维护成本(如修改客户逻辑需深入复杂的Cargo类)。
  • 重构方案
    • 拆分为CustomerGoodsOrder三个类,分别封装对应业务逻辑。
    • main方法中的输入处理逻辑拆分为独立的InputParser类,减少Main类的复杂度。

2. 方法复杂度超标

  • 场景:题目集9的Main.main()方法复杂度为4,输入处理逻辑包含多层嵌套的if-elseswitch,代码可读性差。
    // 简化后的输入处理逻辑(实际更复杂)
    if (customerType.equals("Individual")) {
        // 处理个人客户输入
    } else {
        // 处理企业客户输入
    }
    switch (goodsType) {
        // 处理不同货物类型
    }
    
  • 优化方法
    • 将输入处理逻辑拆分为parseCustomer()parseGoodsList()parseFlight()等独立方法。
    • 使用策略模式或工厂模式解耦不同类型的输入处理,减少条件判断嵌套。

(三)测试与调试经验

1. 边界值测试的重要性

  • 场景:货物计费重量刚好等于临界值(如20kg、50kg)时,费率计算错误。
    • 预期:chargeableWeight=20kg时,应用下一区间费率(如NormalGoods的20kg应属于“<50”区间,费率30)。
    • 实际:因条件判断使用“<”而非“<=”,导致20kg被归为“<20”区间,费率35。
  • 解决方法:修正条件为chargeableWeight < 50改为chargeableWeight <= 50?(需根据业务规则确认,假设规则为“小于”则正确)。

2. 空指针异常调试

  • 场景:未初始化goodsList数组即调用goodsList[i],导致NullPointerException
  • 原因分析:题目集9中,Goods[] goodsList = new Goods[goodsCount];初始化数组后,元素默认值为null,需逐个创建实例。
  • 解决方法:在循环中通过new NormalGoods(...)等语句为数组元素赋值。

3. 输入流残留问题

  • 场景:使用scanner.nextInt()后未消耗换行符,导致后续scanner.nextLine()读取空行。
  • 解决方法:在nextInt()后添加scanner.nextLine()手动消耗换行符,或统一使用scanner.nextLine()读取所有输入并自行解析。

(四)数据对比:两次题目集错误统计

错误类型 题目集8 题目集9 改进措施
编译错误(语法) 5 2 加强IDE语法检查,编写测试用例
逻辑错误(费率) 3 1 增加边界值测试,使用单元测试框架
空指针异常 2 0 严格遵循对象初始化流程
注释缺失 严重 中等 强制要求方法注释和类注释

四、改进建议:代码质量与可维护性提升

(一)代码结构优化

  1. 增加注释覆盖率

    • 为每个类添加功能说明(如@author@version@description)。
    • 为复杂方法(如getRate()calculatePaymentAmount())添加逻辑注释,说明条件判断依据。
    /**
     * 根据计费重量计算运费费率(规则:<20kg费率35,20-50kg费率30,依此类推)
     */
    public double getRate() { ... }
    
  2. 提取工具类

    • 将输入处理逻辑(如Main.main()中的Scanner操作)封装为InputUtility类(也是老师提过的,但是平时写的时候就想偷懒,不过这对检验输入十分方便),提供静态方法parseCustomer()parseGoods()等。
    • 将格式化输出逻辑封装为OutputFormatter类,统一处理订单信息的打印格式。
  3. 使用设计模式

    • 工厂模式:创建CustomerFactoryGoodsFactory,通过类型参数生成具体对象,减少main方法中的if-else逻辑。
    • 策略模式:将不同货物的费率计算策略封装为独立类(如NormalRateStrategy),通过接口RateStrategy实现多态,便于动态切换费率规则。

(二)业务逻辑优化

  1. 参数校验增强

    • 在类的构造方法中添加参数合法性校验(如客户ID、货物重量不能为负数),通过IllegalArgumentException抛出异常。
    public Goods(int id, String name, double weight) {
        if (id <= 0 || weight < 0) {
            throw new IllegalArgumentException("ID和重量必须为正数");
        }
        // ...
    }
    
  2. 异常处理完善

    • main方法中添加try-catch块,捕获InputMismatchException等运行时异常,提示用户输入格式错误。
    try {
        // 输入处理逻辑
    } catch (InputMismatchException e) {
        System.out.println("输入格式错误,请重新运行程序并按提示输入!");
    }
    
  3. 性能优化

    • 避免重复计算:在Goods类中缓存getChargeableWeight()结果,避免多次调用时重复计算体积重量和比较操作。
      private double chargeableWeight; // 添加缓存属性
      
      public double getChargeableWeight() {
          if (chargeableWeight == 0) { // 首次调用时计算
              chargeableWeight = Math.max(weight, calculateVolumeWeight());
          }
          return chargeableWeight;
      }
      
    • 减少循环内的方法调用:在Order.calculateTotalWeight()中,预先将goods.getChargeableWeight()结果保存到局部变量,避免多次访问对象方法。
      public double calculateTotalWeight() {
          double totalWeight = 0;
          for (Goods goods : goodsList) {
              double cw = goods.getChargeableWeight(); // 局部变量缓存
              totalWeight += cw;
          }
          return totalWeight;
      }
      

(三)代码规范与协作改进

  1. 统一命名规范

    • 类名使用驼峰命名法(如FlightInformation而非Flight),方法名使用动词开头(如calculateShippingFee而非getFee)。
    • 避免单字母变量(如将i改为indexgoodsType改为cargoCategory),提升代码自解释性。
  2. 单元测试集成

    • 使用JUnit框架为核心方法编写测试用例,覆盖边界值、正常场景和异常场景。例如:
      @Test
      public void testNormalGoodsRate() {
          Goods normalGoods = new NormalGoods(1, "Book", 10, 20, 30, 15); // 体积重量=10*20*30/6000=1kg,计费重量=15kg
          assertEquals(35, normalGoods.getRate(), 0.1); // <20kg,费率35
      }
      
      @Test
      public void testOrderOverload() {
          Flight flight = new Flight("CA123", "PEK", "SHA", "2025-05-21", 100); // 最大载重100kg
          Goods heavyGoods = new NormalGoods(1, "Machine", 50, 50, 50, 200); // 计费重量=50*50*50/6000≈20.8kg vs 200kg→200kg
          Order order = new Order(1, "2025-05-21", "...", new CorporateCustomer(...), flight, new Goods[]{heavyGoods}, ...);
          assertTrue(order.isOverloaded()); // 200kg > 100kg,应超载
      }
      

五、总结:面向对象设计的进阶与反思

(一)学习成果总结

  1. 面向对象设计能力提升

    • 掌握抽象类与接口的适用场景:抽象类用于定义有共同实现的业务骨架(如Goods的体积重量计算),接口用于定义纯行为契约。
    • 理解多态的核心价值:通过“父类引用指向子类对象”,实现代码的可扩展性。
  2. 复杂业务建模实践

    • 学会将现实问题拆解为类模型,通过类间关系(继承、聚合、依赖)模拟业务流程。
    • 掌握分层设计思想:将输入处理(Main)、业务逻辑(Order)、数据模型(Customer/Goods)分离,提升代码可维护性。
  3. 代码质量意识增强

    • 通过SourceMonitor等工具认识到注释缺失、类职责过重等问题,主动采用Javadoc规范和设计模式优化代码结构。
    • 理解“测试先行”的重要性,通过单元测试覆盖核心逻辑,减少运行时错误。

(二)待改进方向

  1. 设计模式的深入应用

    • 目前仅初步使用多态和简单工厂模式,未来需学习观察者模式(如订单状态变更通知)、单例模式(如系统配置管理器)等,进一步提升代码架构的灵活性。
  2. 异常处理与容错机制

    • 当前代码对输入错误处理不足,需完善异常捕获链,提供友好的错误提示(如“请输入有效的客户ID(数字)”)。
  3. 性能优化与资源管理

    • 对于大规模订单数据,当前线性遍历计算总重量的方式效率较低,可考虑并行计算或数据结构优化(如预处理计费重量)。

(三)对课程与作业的建议

  1. 教学内容优化

    • 增加设计模式的案例讲解,帮助自己提前适应企业级开发规范,虽然老师课上教过,但总觉得对此理解不深。
    • 再推荐一个画类图的软件吧,好多同学的不能用了😭。
  2. 作业设计改进

    • 提供更详细的业务规则文档,或者提供一点点代码思路,减少学生对需求的理解偏差。

(四)结语

题目集8-9的“航空货运管理系统”开发是对面向对象编程的综合性考验,从最初的类设计混乱到逐步实现模块化、多态化,过程中经历了多次重构与调试。通过本次实践,深刻体会到“先设计后编码”的重要性——良好的类结构能大幅降低后期维护成本,而草率的实现往往导致“牵一发而动全身”的困境。未来需继续加强设计模式的学习,培养“用模式思考问题”的习惯,并通过持续集成测试确保代码质量。感谢课程提供的实践机会,让理论知识在具体项目中得到淬炼,期待后续学习中挑战更复杂的系统设计!

posted @ 2025-05-24 22:08  丁伶子  阅读(25)  评论(0)    收藏  举报