第二次Blog的总结-航空货运管理系统
一:前言
题目集8~9 以航空货运管理系统为背景,聚焦航空运费计算与订单处理业务,核心考察面向对象编程中的类设计,以及单一职责、里氏代换、开闭、合成复用和依赖倒转五大原则的实际运用,旨在检验开发者构建高内聚、低耦合软件系统的能力。以及两次题目集的迭代,引领我们关注类职责的细化拆分、系统扩展性设计、继承体系的合理性,让我们对类与类之间的关系有了更深入的了解,引导我们从 “功能实现” 向 “可维护、可扩展的系统架构设计” 进阶。
1. 知识点
以航空货运管理系统为载体,核心考察面向对象编程的类设计与五大设计原则的运用。其中包括根据货物重量与体积计算计费重量、结合货物类型费率和用户折扣率算出基础运费等业务逻辑的类实现;同时要求严格遵循单一职责原则拆分类功能,运用里氏代换原则确保继承体系的可靠性,通过开闭原则实现系统的可扩展性,借助合成复用原则提升代码复用性,利用依赖倒转原则降低类间耦合度,全面检验设计者的架构设计与编程实践能力。
2. 题量
两次题目集分别都只有3道题,点线面问题重构,雨刷程序功能扩展设计,魔方,点线面的再重构问题,以及两次航空货运管理系统的类设计,题量不太大。
3. 难度
相较于5~7题目集考察思维逻辑来说,这次题目集对类设计的考察,难度下降了很多,但是对于如何设计才能使代码运行更高效,又对我们提出来新的挑战。
二:设计与分析
1. 第一次作业
题目要求:
某航空公司“航空货运管理系统”中的空运费的计算涉及多个因素,通常包 括货物重量/体积、运输距离、附加费用、货物类型、客户类型以及市场供需等。 本次作业主要考虑货物重量/体积。
输入格式:
按如下顺序分别输入客户信息、货物信息、航班信息以及订单信息。

输出格式:
• 如果订单中货物重量超过航班剩余载重量,程序输出The flight with flight number:航班号 has exceeded its load capacity and cannot carry the order. ,程序终止运行。
• 如果航班载重量可以承接该订单,输出如下:

类图:

类设计思路:
Person类:一开始设计的时候还没有想到单独抽出来一个人物类出来,但是在我读题目时发现客户有姓名,电话,地址,发件人,收件人也有姓名,电话,地址,因此我打算抽出一个人物类,用来封装姓名,电话,地址这些个人通用信息,作为Customer、Sender、Receiver类的父类。
Customer类:分析输入输出,客户要提供姓名电话等个人信息,但是不需要参与额外的计算,于是我把客户分为一个单独的类,继承Person类,存放客户的个人信息。
Sender类和Receiver类:跟Customer类一样,仅需提供个人信息,所以把它们也单独作为一个类。
Goods类:因为要输出货物明细报表,因此我设计了货物类,用于描述货物信息,包含goodsid、goodsName、width、length、height、weight等属性及操作方法,对货物信息的抽象封装,便于管理和操作货物相关数据。
Order类:根据题目要求的货物明细报表,不止一个货物,于是我设计了订单类,通过LinkedList<Goods>属性管理多个货物,提供添加货物和计算总重量等方法,实现了对订单中货物集合的管理。
ClientDetails类:题目还要求输出订单信息报表,需要航班号,订单号,发件人,收件人的信息等,于是我又设计了一个客户明细类,整合了orderId、orderDate等订单信息,以及Sender、Receiver、Payment等相关对象,将订单涉及的发货人、收货人、支付方式等信息聚合,形成完整订单详情类。
Payment类:题目需要选择微信支付还是支付宝支付,于是我设计了一个抽象类,定义支付相关的抽象方法pay()。
WeChat类和Alipay类:继承自Payment类,分别实现微信和支付宝的具体支付逻辑。
Flight类:将提供的航班的flightId、originAirport、arrivalAirport、flightDate、maxWeight等信息及操作方法封装起来,用于管理航班信息。
Controller类:空运费计算需综合考虑货物重量 / 体积、结合不同货物类型费率 。因此我设计了控制类,持有Order对象,提供获取订单、计算(可能涉及运费等计算逻辑)、获取日期等方法,能够协调和控制订单相关业务逻辑。
设计原则的体现:
• 单一职责原则
类功能明确:每个类专注于特定功能。如Goods类仅负责描述货物信息,不涉及订单管理或支付等其他功能;Flight类只管理航班的flightId、originAirport等航班相关信息,做到一个类一个主要职责,降低类的复杂度,提高代码的可维护性。
方法职责单一:以Order类为例,addGoods(Goods goods)方法专门用于向订单中添加货物,getTotalWeight()专注计算订单货物总重量,方法功能单一,避免方法内逻辑混杂。
• 里氏代换原则:Customer、Sender、Receiver类继承自Person类,它们可以在程序中替代Person类使用。
• 开闭原则:Payment为抽象类,定义了pay()抽象方法。WeChat和Alipay类继承自Payment并实现pay()方法。
• 合成复用原则:ClientDetails类中,通过组合Sender、Receiver、Payment等对象来构建完整订单详情,而非继承这些类。
• 依赖倒转原则:Payment作为抽象类,WeChat和Alipay子类依赖于抽象的Payment。
复杂度分析:
Metrics Details For File '航班管理1.java' -------------------------------------------------------------------------------------------- Parameter Value ========= ===== Project Directory D:\电梯代码\航务管理系统1\ Project Name 8 Checkpoint Name 8 File Name 航班管理1.java Lines 525 Statements 166 Percent Branch Statements 0.6 Method Call Statements 18 Percent Lines with Comments 1.7 Classes and Interfaces 4 Methods per Class 14.50 Average Statements per Method 2.55 Line Number of Most Complex Method 11 Name of Most Complex Method Sender.Sender() Maximum Complexity 1 Line Number of Deepest Block 16 Maximum Block Depth 2 Average Block Depth 0.66 Average Complexity 1.00 -------------------------------------------------------------------------------------------- Most Complex Methods in 3 Class(es): Complexity, Statements, Max Depth, Calls Person.getAddress() 1, 1, 2, 0 Person.getName() 1, 1, 2, 0 Person.getPhone() 1, 1, 2, 0 Person.Person() 1, 3, 2, 0 Person.Person() 1, 0, 0, 0 Person.setAddress() 1, 1, 2, 0 Person.setName() 1, 1, 2, 0 Person.setPhone() 1, 1, 2, 0 Receiver.Receiver() 1, 1, 2, 1 Receiver.Receiver() 1, 0, 0, 0 Sender.Sender() 1, 1, 2, 1 Sender.Sender() 1, 0, 0, 0 --------------------------------------------------------------------------------------------

主要问题:
1. 方法复杂度不均衡:从 “Most Complex Methods” 列表看,部分方法复杂度虽为 1 看似简单,但像Person.Person() 、Sender.Sender() 、Receiver.Receiver() 等方法存在两种不同复杂度表现(有语句数为 0 和非 0 情况 ),意味着代码逻辑不统一或存在冗余定义,后期维护易混乱。
2. 最大块深度和平均块深度:最大块深度为 2 ,平均块深度 0.66,虽不算高,但结合方法复杂度和语句分布,在嵌套逻辑处理上不够简洁。
3. 平均语句数较低但分布不均:平均每个方法语句数为 2.55,整体较低。不过在不同方法中语句数差异大,如Person.Person() 有 3 条语句,部分方法有 1 条,还有为 0 的,可能导致方法功能划分过细或过粗,不利于代码理解与复用。
4. 注释比例低:带注释的行数百分比仅 1.7 ,代码缺乏注释会使代码逻辑难理解,尤其是团队协作或后期自己维护时,增加解读成本,不利于代码长期维护和迭代。
改进心得:
第一次做设计的时候没有仔细观察代码,没能发现有些地方可以进行再拆分,导致方法冗余,结构变得复杂。因此我期望在后续的代码里可以改进以下方面。
1. 消除代码冗余:提取重复代码为公共方法,比如将计算体积重量和计费重量的逻辑提取到一个独立方法中,在Order 类和Controller 类中调用,降低代码复杂度,优化雷达图中的复杂度指标。
2. 添加注释:在关键代码段、复杂业务逻辑处添加注释,说明代码功能和设计意图,提高代码可读性,增加注释比例,优化 “% Comments” 指标。
2. 第二次作业
题目要求:
航空快递以速度快、安全性高成为急件或贵重物品的首选。本题目要求对航空货运管理系统进行类设计,要求满足面向对象设计原则中的单一职责原则、里氏代换原则、开 闭原则以及合成复用原则、依赖倒转原则。
输入格式:
按如下顺序分别输入客户信息、货物信息、航班信息以及订单信息。

输出格式:
• 如果订单中货物重量超过航班剩余载重量,程序输出The flight with flight number:航班号 has exceeded its load capacity and cannot carry the order. ,程序终止运行。
• 如果航班载重量可以承接该订单,输出如下:

类图:


类设计思路:
1. Good类新增:根据题目计算运费需要根据不同的货物类型来计算。所以我在Good类的构造方法参数新增了GoodsStyle(货物类型),并增加了对应的GoodsStyle()和setGoodsStyle()方法。补充了货物类型的分类信息,使商品实体的属性更完整,用于区分不同运输规则(如易碎品、普通货物等)。
2. Order类新增:题目又新加了根据不同的客户类型计算折扣,所以我在Order类中新增了setCustomStyle()方法。用于设置订单的客户类型(如个人用户和集团用户),结合运费计算逻辑,可支持不同客户等级的费率优惠,增强了订单业务的灵活性。
3. 新增Cash类:基于第一次作业支付方式已经做成了抽象类,第二次作业要求增加了现金支付选项,我就直接新增了一个Cash类继承抽象类支付方式,不需要额外改动代码。
设计原则的体现:
• 单一职责原则:Goods类仅负责商品属性(类型、尺寸、重量等)的封装,职责单一。Payment抽象类及其子类(WeChat、Alipay、Cash)仅处理支付逻辑,符合单一职责。
• 里氏代换原则:Customer、Sender、Receiver均继承自Person,并复用其父类的属性(姓名、电话、地址)和方法(getName、setPhone等),子类对象可替代父类使用。WeChat、Alipay等子类实现Payment抽象类的pay方法,可在需要Payment的场景中无缝替换(如ClientDetails类中使用Payment类型成员)。
• 开闭原则:新增支付方式(如Cash类)时,只需继承Payment抽象类并实现pay方法,无需修改原有支付逻辑。Goods类通过goodsStyle属性支持不同货物类型(如Normal、Dangerous)的运费规则扩展,Controller的getRate方法通过条件判断处理新增类型,虽未完全隔离变化,但避免了对核心计算逻辑的直接修改。
• 合成复用原则:Order类包含LinkedList<Goods>类型的list成员,通过组合管理商品列表,而非继承Goods类。ClientDetails类组合了Sender、Receiver、Payment等对象,体现 “Has-A” 关系,符合合成复用原则。
• 依赖倒转原则:Payment抽象类定义支付接口,WeChat、Alipay等细节类依赖该抽象,高层模块(如ClientDetails、Main)通过Payment接口引用具体支付实现,符合依赖倒转原则。
复杂度分析:
Metrics Details For File 'plane2.java' -------------------------------------------------------------------------------------------- Parameter Value ========= ===== Project Directory D:\电梯代码\航空管理系统2\ Project Name 9 Checkpoint Name 9 File Name plane2.java Lines 592 Statements 182 Percent Branch Statements 0.5 Method Call Statements 18 Percent Lines with Comments 1.5 Classes and Interfaces 5 Methods per Class 12.60 Average Statements per Method 2.59 Line Number of Most Complex Method 11 Name of Most Complex Method Sender.Sender() Maximum Complexity 1 Line Number of Deepest Block 16 Maximum Block Depth 2 Average Block Depth 0.65 Average Complexity 1.00 -------------------------------------------------------------------------------------------- Most Complex Methods in 4 Class(es): Complexity, Statements, Max Depth, Calls Person.getAddress() 1, 1, 2, 0 Person.getName() 1, 1, 2, 0 Person.getPhone() 1, 1, 2, 0 Person.Person() 1, 3, 2, 0 Person.Person() 1, 0, 0, 0 Person.setAddress() 1, 1, 2, 0 Person.setName() 1, 1, 2, 0 Person.setPhone() 1, 1, 2, 0 Receiver.Receiver() 1, 1, 2, 1 Receiver.Receiver() 1, 0, 0, 0 Sender.Sender() 1, 1, 2, 1 Sender.Sender() 1, 0, 0, 0 WeChat.pay() 1, 1, 2, 0 --------------------------------------------------------------------------------------------

主要问题:
1. 方法复杂度不均衡:部分方法(如Person.Person() 、Sender.Sender() 、Receiver.Receiver() )存在两种不同复杂度表现(语句数有 0 和非 0 情况 ),意味着代码逻辑不统一,存在冗余定义或功能划分混乱的问题,不利于后期维护和代码的稳定性。
2. 块深度相关问题:虽然最大块深度为 2 ,平均块深度 0.65 不算高,结合方法复杂度和语句分布情况来看,嵌套逻辑处理不够简洁高效,可能会影响代码的执行效率和可维护性。
3. 代码注释匮乏:带注释的行数百分比仅 1.5% ,注释严重不足。这会导致代码可读性差,尤其是在复杂业务逻辑或关键代码段处,难以快速理解代码意图和功能逻辑,增加解读成本和维护难度。
相较于第一次改进的地方:
平均块深度:提取重复代码为公共方法,将计算体积重量和计费重量的逻辑提取到一个独立方法中。使得第一次分析中平均块深度为 0.66,此次为 0.65 ,嵌套逻辑处理上有优化,使整体平均块深度稍有降低。
改进心得:
1. 重构复杂方法:对Person.Person() 、Sender.Sender() 、Receiver.Receiver() 等存在复杂度差异的方法进行审查和重构。明确方法功能,删除冗余方法;若方法功能有遗漏,补充完整逻辑;对于功能相近的方法,尝试合并或统一逻辑,使代码逻辑更清晰、简洁。
2. 优化方法功能划分:对语句数差异较大的方法,重新审视其功能。将功能过于复杂的方法拆分成多个功能单一的方法;对于功能简单且相近的方法,考虑是否可以合并,使方法功能划分更合理,提高代码的可理解性和复用性。
三:踩坑心得
1. 在一开始设计的时候,除了基础的大纲时,在编写代码时对于输出格式一筹莫展,因为货物明显报表需要每个货物的计费重量,以及要针对每个货物的计费重量进行匹配运率,一直不知道怎么设计,浪费了很多时间,后来才知道可以将方法做成集合的形式,将要算的存入集合就很好的解决了问题,对于方法的类型我要多多思考可能性,不要只局限于double 和void 类型。
2. 在第二次作业中,对一些细节的把控也没做好。

有一次提交时,有非零返回,于是我针对测试点进行排查,一开始以为是我关于计费重量或者计算运费算错误了,又输入不同的测试用例发现,在输入现金支付时,异常情况处理不对,我才发现是判断支付方式的switch 语句缺陷。在现金支付那里少了一个break语句。以后对于细节的把控要注意反思。
错误的代码:

改正的代码:

3. 在设计时,也要注意代码的冗余。可以多观察下有没有共同的代码,整合在一起,能大大降低代码的复杂度。
第一次关于计算运费和计算运率的代码:


第二次作业:


将获取lastWeight提取出一个方法,以后计算运费和计算运率只需调用这个方法,减少了很多代码量。
四:改进建议
1. 降低整体复杂度:提取重复代码。如Controller类中计算运费、运率等方法存在重复计算体积重量等逻辑,将这些公共逻辑提取成独立方法,降低方法复杂度,进而优化平均复杂度指标。
2. 优化块深度:简化嵌套逻辑。在计算运费和计算运率时,大幅运用了if - else 等嵌套结构,可以通过合并条件、提前返回等方式减少嵌套层数,降低最大块深度和平均块深度。
3. 大幅增加注释:当前注释比例极低,在类、方法以及复杂逻辑代码段添加注释。类注释说明功能和职责;方法注释写明输入、输出、功能;复杂逻辑注释解释实现思路。如在计算运费的方法中,注释每段代码的计算目的。
五:总结
1. 学习收获
面向对象设计原则的实践
类设计与业务逻辑的解耦
• 明确类的职责划分,如Goods类仅封装货物属性,Order类管理货物集合,Controller类专注运费计算,避免类职责混杂,符合单一职责原则。
• 掌握通过提取1公共方法(如将计算计费重量的逻辑提取为caculateLastWeights)减少代码冗余,降低方法复杂度。
代码复杂度与可读性的优化
• 意识到方法复杂度不均衡(如构造方法存在语句数为 0 的冗余定义)和注释匮乏(注释比例仅 1.5%-1.7%)的问题,需通过重构和添加注释提升代码可维护性。
2. 需进一步学习及研究的方向
设计模式的深入应用
• 目前对策略模式(如不同货物类型的费率计算)和工厂模式(如对象创建逻辑)的应用仍停留在基础层面,需进一步学习如何通过设计模式完全隔离变化,避免Controller 中大量条件判断,更彻底地遵循开闭原则。
异常处理与代码健壮性
• 输入验证逻辑薄弱(如未处理非法输入类型),导致程序易崩溃(如非零返回错误)。需系统学习 Java 异常处理机制,在关键业务节点添加输入校验和异常捕获。
性能优化与代码规范
• 对集合类(如LinkedList与ArrayList)的性能差异理解不足,需深入研究数据结构特性,选择更优容器提升代码效率。

浙公网安备 33010602011771号