题目集8~9总结性Blog

一·前言

1.两次题目集设计中包括了继承与多态,还有抽象类的设计与调用
(1)继承:继承是指一个子类(派生类)可以继承其父类(基类)的属性和方法,通过extends关键字实现。它的主要作用是实现代码的复用,避免重复编写相同的代码,同时建立类之间的层次关系,使程序结构更加清晰。Java 中一个子类只能继承一个父类,避免了多重继承带来的复杂性和冲突。
(2)多态:多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在 Java 中,多态主要通过方法重载和方法重写实现。多态使程序具有更好的扩展性和可维护性。当程序需要添加新的子类时,不需要修改调用代码,只需要在子类中实现相应的方法即可。
(3)抽象类:抽象类是使用abstract关键字修饰的类,它不能被实例化,只能作为父类被其他类继承。抽象类中可以包含抽象方法和具体方法,抽象方法只有声明,没有实现,必须由子类重写;具体方法有方法体,子类可以直接继承使用。

2.题目的设计要素中涉及到多项原则
(1)单一职责原则:一个类只负责一项职责。如果一个类承担了过多的职责,那么当其中一个职责发生变化时,可能会影响到其他职责的正常运行,从而增加代码的维护难度和出错风险。例如雨刷程序功能扩展设计题目中创建了雨刷类,控制杆类,刻度盘类,控制类,分区管理。
(2)里氏替换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象,也就是说子类对象必须能够替换掉它们的父类对象,并且程序的行为不会发生改变。这要求子类必须实现父类的抽象方法,同时不能重写父类的非抽象方法(除非方法签名完全相同且逻辑兼容),且子类的前置条件不能强于父类,后置条件不能弱于父类。例如点线面问题重构题目中点Point类,面Plane类和线Line类继承抽象Element类。例如雨刷程序功能扩展设计题目中雨刷系统类1和雨刷系统类2继承抽象的雨刷系统类。
(3)开闭原则:在不修改原有代码的基础上,通过扩展代码来实现新的功能需求,能够有效提高软件的可维护性和可扩展性。例如点线面问题重构题目中若要修改子类Point,Plane,Line类,对其添加属性或者方法,对已经定义的抽象Element类中的方法修改即可。
(4)合成复用原则:尽量使用合成 / 聚合(组合)的方式,而不是使用继承来达到复用的目的。组合是一种 “有一个” 的关系,一个类包含另一个类的对象作为成员变量;聚合是一种 “整体与部分” 的弱组合关系。通过组合和聚合,可以将已有的对象组合到新对象中,实现代码复用。
(5)依赖倒转原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单来说,就是要针对接口编程,而不是针对实现编程,通过依赖抽象来降低模块间的耦合度。

3.这两次题目集的题量合适,部分题目是在之前题目集的题目基础上进行改动扩展。对题目要求理解起来比较快,把握代码编写的主要方向。

4.继承与多态深化题目涉及复杂的类继承层次和多态行为,需要清晰把握类之间的关系和方法调用逻辑。抽象类设计与应用题目要求深入理解抽象类特性,并合理设计抽象方法与具体子类实现。有难点也有难度合适的部分。

二·设计与分析

题集八



1.代码规模相关
(1)行数与语句数:代码总行数为 325 行,语句数量是 197 条 。行数较多但语句数相对少些,可能存在一些空白行或长语句。这一规模对于小型项目或许合适,但随着功能扩展可能需要合理拆分 。
(2)方法调用:有 50 次方法调用,说明代码中不同功能模块间存在一定交互,调用频繁度适中,若调用逻辑混乱可能影响代码可读性和维护性 。

2.代码结构特征
(3)分支语句:分支语句占比 7.1% ,比例较低,表明代码中条件判断等逻辑分支不算复杂,代码执行路径相对简单,理解和调试难度较小 。
(4)注释占比:注释行占比仅 3.7% ,过少的注释会使代码可读性变差,后续维护人员难以快速理解代码意图,尤其当业务逻辑复杂时,增加注释很有必要 。
(5)类与方法:只有 1 个类,类中平均有 35 个方法,平均每个方法含 5.37 条语句 。类方法数量较多,可能存在职责不够单一的问题,违反了单一职责原则,不利于代码的复用和维护;不过方法内语句数不算多,一定程度上便于方法功能的理解 。

3.代码复杂度相关
(1)圈复杂度:最大圈复杂度为 4 ,平均圈复杂度也是 4.00 。圈复杂度在 1 - 10 属于相对简单可控范围,说明代码逻辑分支情况总体良好,方法逻辑不算复杂,可维护性和可测试性相对较高 。
(2)代码块深度:最大代码块深度为 3 ,平均深度 0.97 。最大深度表明代码中最深的嵌套层次为 3 层,不算太深;平均深度低说明整体代码嵌套情况不严重,代码结构较为扁平,利于理解和修改 。

4.各主要类分析
(1)Flight类
属性:有航班号(flightNumber )、出发机场(departureAirport )、到达机场(arrivalAirport )、航班日期(flightDate )、最大载重(maxLoad ) 。这些属性描述了航班的基本信息,用于标识和管理航班资源 。
方法:构造函数用于初始化航班信息;setFlightNumber等 setter 方法用于修改属性值;getMaxLoad获取最大载重;printFlightInfo用于打印航班信息 。这些方法提供了对航班信息的操作和展示功能。
(2)Order类
属性:包含订单编号(orderId )、订单日期(orderDate )、发货人地址(senderAddress )等诸多信息,还关联了货物数组(cargos ) 。全面记录订单相关的发货、收货以及货物情况 。
方法:构造函数用于创建订单并初始化相关信息;各类 setter 和 getter 方法用于操作属性;calculateChargeableWeight计算计费重量 、calculateTotalAmount计算订单总金额等方法实现订单业务逻辑;printOrderInfo和printCargoDetails用于展示订单和货物详情 。
(3)Cargo类
属性:有货物编号(cargoId )、货物名称(cargoName )、宽(width )、长(length )、高(height )、重量(weight )、体积(volume )、计费重量(chargeableWeight )、费率(rate ) 。详细描述货物自身特征和计费相关属性 。
方法:构造函数初始化货物信息;calculateVolume计算体积、calculateChargeableWeight计算计费重量等方法实现货物相关业务计算;各类 setter 和 getter 方法用于属性操作;printCargoDetails打印货物详情 。
(4)Customer类
属性:包括客户编号(customerId )、客户姓名(customerName )、客户电话(customerPhone )、客户地址(customerAddress ) 。记录客户基本联系信息 。
方法:构造函数初始化客户信息;各类 setter 和 getter 方法用于获取和修改客户属性 。

5.心得
(1)通过定义Flight、Order、Cargo、Customer等类,将现实业务中的实体抽象为代码中的对象,理解如何用属性描述事物状态(如航班号、货物重量),用方法封装行为逻辑(如计算计费重量、打印订单信息)。
(2)学会从业务需求中提炼类的职责,例如Order类专注于订单信息管理和金额计算,Cargo类负责货物属性的初始化与计算,体现 “高内聚” 原则。
(3)通过private修饰符隐藏类的属性,仅通过public的 setter/getter 方法暴露必要的访问接口,避免外部直接修改属性值导致数据混乱,增强了代码的健壮性。
例如,Flight类的maxLoad属性通过getMaxLoad方法读取,确保载重信息的一致性。
(4)若后续需要支持不同类型的货物(如普通货物、危险品、加急件)或客户(企业客户、个人客户),可通过继承抽象类(如Cargo、Customer)并实现多态,避免在单一类中编写大量条件判断。
例如,不同货物类型重写calculateRate方法,不同客户类型重写getDiscountRate方法,使代码具备可扩展性

题集九




1.代码规模相关
行数与语句数:代码总行数为 416 行,语句数量是 186 条 。行数较多而语句数相对较少,可能存在较多空白行或长语句。此规模对于小型项目尚可,但功能扩展时可能需合理拆分 。
方法调用:有 39 次方法调用,说明代码中功能模块间存在交互,调用频繁度适中,若调用逻辑混乱会影响代码可读性与维护性 。
2.代码结构特征
分支语句:分支语句占比 10.8% ,相较于之前略高,表明代码中条件判断等逻辑分支有所增加,代码执行路径相对复杂一些,理解和调试难度稍有提升 。
注释占比:注释行占比仅 2.9% ,注释过少会严重影响代码可读性,后续维护人员难以快速理解代码意图,尤其业务逻辑复杂时,急需增加注释 。
类与方法:有 4 个类,平均每个类有 9.25 个方法,平均每个方法含 4.57 条语句 。类数量较多且平均方法数也不少,可能存在类职责划分不清晰问题,不利于代码复用和维护;不过方法内语句数不算多,一定程度上便于理解方法功能 。
3.代码复杂度相关
圈复杂度:最大圈复杂度为 5 ,平均圈复杂度是 1.40 。最大圈复杂度相对适中,平均圈复杂度较低,说明大部分方法逻辑分支简单,但存在个别相对复杂方法(如ExpediteCargo.calculateRate() ),需关注复杂方法的维护和优化 。
代码块深度:最大代码块深度为 3 ,平均深度 1.15 。最大深度表明代码中最深嵌套层次为 3 层,不算太深;平均深度低说明整体代码嵌套情况不严重,代码结构较为扁平,利于理解和修改 。
最复杂方法情况:列出了多个类中较复杂方法及其圈复杂度、语句数、最大深度和方法调用数等信息。如ExpediteCargo.calculateRate()方法圈复杂度为 5 ,语句数 9 ,最大深度 3 ,方法调用数 5 ,说明该方法逻辑相对复杂,后续维护或修改时需重点关注 。

4.各主要类分析
(1)Cargo类(抽象类)
属性:包含货物编号(cargoId )、货物名称(cargoName )、宽(width )、长(length )、高(height )、体积(volume )、计费重量(chargeableWeight )、费率(rate ) 。这些属性用于描述货物的基本特征和计费相关信息 。
方法:构造函数用于初始化货物信息;calculateVolume计算体积、calculateChargeableWeight计算计费重量、calculateRate计算费率等方法实现货物相关业务计算(部分为抽象方法,需子类实现 );各类 setter 和 getter 方法用于属性操作;printCargoDetails打印货物详情 。作为抽象类,为具体货物类型提供了通用的属性和方法框架 。
(2)NormalCargo类
继承自Cargo类 ,通过构造函数传递参数调用父类构造函数初始化属性,并覆盖(override )了calculateRate方法,实现普通货物的费率计算逻辑 。
DangerousCargo类:同样继承自Cargo类 ,构造函数初始化属性,覆盖calculateRate方法,用于实现危险货物的费率计算,因其特殊性,费率计算逻辑可能与普通货物不同 。
(3)ExpediteCargo类
继承自Cargo类 ,构造函数初始化属性并覆盖calculateRate方法,实现加急货物的费率计算,可能会考虑加急因素调整费率 。
客户相关类
(4)Customer类(抽象类)
属性:有客户编号(customerId )、客户姓名(customerName )、客户电话(customerPhone )、客户地址(customerAddress ) 。记录客户基本联系信息 。
方法:构造函数初始化客户信息;各类 setter 和 getter 方法用于获取和修改客户属性;printCustomerInfo打印客户信息;getDiscountRate获取折扣率(抽象方法,由子类实现 ) 。为不同类型客户提供通用属性和方法定义 。
(5)CorporateCustomer类
继承自Customer类 ,构造函数初始化属性并调用父类构造函数,覆盖getDiscountRate方法,实现企业客户的折扣率计算逻辑,企业客户可能因业务量大等因素有特定折扣策略 。
(6)IndividualCustomer类
继承自Customer类 ,构造函数初始化属性并调用父类构造函数,覆盖getDiscountRate方法,实现个人客户的折扣率计算,个人客户折扣策略可能与企业客户不同 。
(7)Order类
属性:包含订单编号(orderId )、订单日期(orderDate )、发货人地址(senderAddress )等诸多发货和收货相关信息,关联货物数组(cargos )、航班(flight )、支付方式(paymentMethod )、客户(customer ) 。全面记录订单相关信息,是业务流转的核心载体 。
方法:构造函数用于创建订单并初始化相关信息;各类 setter 和 getter 方法用于操作属性;calculateTotalWeight计算总重量、calculateTotalAmount计算订单总金额等方法实现订单业务逻辑;printOrderInfo和printCargoDetails用于展示订单和货物详情 。
(8)Flight类
属性:有航班号(flightNumber )、出发机场(departureAirport )、到达机场(arrivalAirport )、航班日期(flightDate )、最大载重(maxLoad ) 。描述航班基本信息,用于航班资源管理 。
方法:构造函数用于初始化航班信息;setFlightNumber等 setter 方法用于修改属性值;getMaxLoad获取最大载重;printFlightInfo用于打印航班信息 。
PaymentMethod类(枚举类):定义了支付方式枚举常量,包括微信(Wechat )、支付宝(AliPay )、现金(Cash ) 。为订单支付提供标准化的支付方式选项 。

5.编码心得
(1)通过定义抽象类Cargo和Customer,将共性属性(如货物基础信息、客户联系信息)和行为(如计算费率、获取折扣率)封装在父类,子类(如NormalCargo、CorporateCustomer)仅需重写差异化逻辑。这显著减少了代码冗余,例如三种货物类型共享体积计算逻辑,仅需在父类实现一次calculateVolume方法。
(2)各个类的职责划分相对清晰,如Order类专注订单信息管理和业务计算,Flight类负责航班信息维护,便于代码的理解、维护和扩展 。
(3)添加了枚举类型,PaymentMethod枚举类的使用,使支付方式定义清晰、规范,避免了字符串等不规范表示带来的问题,提高了代码的可读性和可维护性 。
(4)题目对不同类型的顾客在计费上给出了不同的数据,通过重写calculateRate方法实现各自费率规则,不同客户类型通过重写getDiscountRate方法实现差异化折扣。

三·采坑心得:

  1. 数据输入与格式处理问题,在输入包含多个字符串和数值的混合数据时,Scanner的nextLine()方法容易读取到残留的换行符,导致输入逻辑混乱。例如,在输入完货物数量(整数)后直接调用nextLine(),会读取到上一行输入的换行符而非实际字符串数据。在每次读取整数后,手动调用scanner.nextLine()消耗残留的换行符,确保后续nextLine()能正确读取字符串。
  2. 日期格式解析异常,在解析航班日期和订单日期时,若输入的日期格式与SimpleDateFormat定义的格式(如yyyy-MM-dd)不一致,会抛出ParseException,但代码中仅通过e.printStackTrace()打印异常,未提供友好的错误提示或重试机制。
    3.一开始对几个原则的使用不到位,没充分理解原则的需求,例如合成复用原则,在对其查询资料可知,合成复用原则通过 “组合优于继承” 的设计思想,降低类间耦合,提高代码可维护性和可扩展性。在设计中,应优先将现有对象作为新对象的组成部分,而非通过继承直接扩展,从而构建更灵活、健壮的系统。
    4.代码遵循了单一职责原则,Customer类:专注于客户信息的管理和折扣率计算,不涉及订单或货物逻辑。Cargo类:负责货物属性和基本运费计算,子类(如NormalCargo)通过重写calculateRate()实现特定费率规则。Order类:处理订单相关信息(如发货人、收货人、支付方式)和总金额计算,依赖其他类完成具体功能。
    5.代码遵循了开闭原则,题集9里面将原来的货物类和客户类变为抽象类,Customer抽象类:新增客户类型(如 VIP 客户)只需继承Customer并重写getDiscountRate(),无需修改现有类。Cargo抽象类:新增货物类型(如冷藏货物)只需继承Cargo并重写calculateRate(),不影响现有代码。枚举类PaymentMethod:新增支付方式(如银行卡)只需扩展枚举值,无需修改Order类。
    6.遵循里氏替换原则,IndividualCustomer和CorporateCustomer:均继承自Customer,可无缝替换父类使用(如Order类中通过customer.getDiscountRate()调用)。NormalCargo、DangerousCargo、ExpediteCargo:均继承自Cargo,在Order类的calculateTotalAmount()方法中可统一处理。
    7.遵循了依赖倒置原则,例如Order类依赖抽象,Order类通过customer.getDiscountRate()和cargo.getBasicFreight()调用抽象方法,无需关心具体实现
    8.在代码编写完成后,提交时出现了非零返回,代码格式没有错误,运行结果错误,反复检查后发现在获取输入时货物类别与货物信息放在一起读取输入,出现问题,好再修改后能够正常运行
    9.在编写题集八的“航空货运管理系统”时,未使用开闭原则,题目所需的类的种类比较少,未找到很好的继承关系,但在题集九,客户分为了个人客户和集体客户,有略微的区分度,可以将两者统一继承一个客户类,较好的运用了该原则。货物类也分为普通货物类,加急货物类和危险货物类,继承货物类,同时将其抽象化,调整里面方法之间的关系,不断分类整理思路

四·改进建议

1.Order类混合职责:Order类既处理订单信息管理,又负责报表打印(如printOrderInfo),可以新增报表生成类,接收Order对象,负责生成订单信息和货物明细报表,使Order类专注于业务逻辑。
2.当前Main类承担了输入处理、对象创建、业务逻辑校验等多项职责,可以新增输入处理类,专门负责从键盘读取数据并校验格式,返回结构化数据(如Map<String, String>)。
3.可以在代码中多增加一些注释,为关键类、方法和参数添加注释,说明功能、参数含义和异常情况增强代码的可读性
4.通过遵循单一职责原则拆分臃肿类、利用开闭原则通过抽象和策略模式支持扩展、基于合成复用原则用组合替代继承、依据依赖倒转原则依赖抽象接口,代码将更具灵活性和可维护性。
5.使用枚举优化支付方式:当前PaymentMethod枚举已符合规范,可进一步扩展支付策略(如积分抵扣),通过策略模式实现。
6.添加输入校验,在InputHandler中对货物尺寸、重量、日期格式等进行合法性校验,抛出自定义异常(如InvalidInputException)。
统一异常处理,在主程序中捕获所有异常,提供友好错误提示,避免程序崩溃。
7.高层模块(Order)依赖低层模块(Flight具体类):Order类直接调用Flight的方法,若Flight接口变更,可能影响Order。为Flight类定义接口FlightService,Order类依赖该接口而非具体实现。通过构造函数或 setter 方法注入Customer、Cargo等抽象对象,避免在Order内部直接创建实例。

五·总结

1.通过两次题目集,对单一职责原则(SRP)、开闭原则(OCP)、里氏代换原则(LSP)、合成复用原则(CRP)和依赖倒转原则(DIP)有了直观理解。例如:
通过抽象类Customer和Cargo实现开闭原则,新增客户 / 货物类型时无需修改原有代码;Order类组合Flight、Customer等对象,实践了合成复用原则,避免继承带来的强耦合。

2.意识到设计原则是代码质量的 “指南针”,而非孤立的概念。例如,单一职责原则指导类的拆分(如将输入处理从Main类剥离),依赖倒转原则确保高层模块依赖抽象接口而非具体实现。

3.学会通过抽象类提取共性(如Cargo类封装体积计算、计费重量逻辑),子类专注差异化实现(如NormalCargo重写费率计算)

4.理解继承的适用场景(“is-a” 关系)与组合的优势(“has-a” 关系),例如Order类与Cargo类的关系应为组合而非继承。

5.积累了数据输入输出与异常处理的经验,掌握Scanner的输入处理技巧(如消耗换行符、类型转换),理解输入校验的必要性(如货物重量不能为负数);认识到异常处理是代码健壮性的关键(如日期格式解析异常ParseException的捕获与处理)

6.有了教师的耐心指导,期待后续课程能更深入地融合理论与实践,帮助我们构建完整的技术知识体系。

7.在作业批改中,老师可以针对设计原则的应用情况给出具体评语(如 “此处违反了单一职责原则,建议将打印逻辑拆分为独立类”),对基础薄弱的学生,提供一对一的设计思路辅导,帮助学生发现自身设计盲区,针对性改进编码习惯。

8.设计团队项目,完成题目的扩展(如增加货物跟踪、用户权限管理通过协作实践,深化对设计原则的理解,同时培养沟通与团队协作能力。

  1. 作业中让我们先独立思考设计方案,再结对讨论,最后全班分享,增强参与感。避免单向灌输,通过高频互动加深理解,及时暴露认知误区。

  2. 课下可以构建学习支持体系,收集学生常见问题,发布设计模式常见误区与解决方案;同时希望大家可以相互推荐优质学习资源(如 B 站的相关课程),鼓励学生课外拓展;

posted @ 2025-05-21 16:23  桃玖  阅读(40)  评论(0)    收藏  举报