题目集8-9的总结性Blog

一、前言

又经过了几周的Java课程学习,真的感觉学到了很多。不光是对一些语法知识的巩固,更是对Java独有的一些原则和法则有了更好的认识。这完全让我看到了Java和C语言的不同,上学期完成C语言的pta时,仅仅只是为了答案正确,从而得到分数,而在第一次的大作业基础上,我也开始更加注重控制每个类和方法的长度和圈复杂度的降低,并结合开闭原则、单一职责原则等设计原则,既满足物流订单管理系统的业务需求,又确保代码具备良好的扩展性、可维护性和健壮性。在这次实现物流订单管理系统时,我也深刻体会到了老师上课说的工厂模式的强大之处,从一开始只知道混乱的对象创建,到通过建立工厂类来实现,整个过程带给我了很多的收获。这次的大作业虽然要求的功能与技术会更多。其中,继承与多态让不同类型的支付方式、货物实现差异化功能的同时,保持统一的调用接口;抽象类与接口则定义了各模块的通用规则和行为,确保代码结构清晰、易于扩展。这些技术点相互配合、,有效解决了开发过程中遇到的诸多问题。但是这次大作业给我的感觉只是类与对象,属性很多,但内部逻辑会比上一次的简单很多,所有的要求都很清晰明了,刚拿到题目的时候,思路也没有很混乱,所以总体来说这次的大作业完成的还是比较顺利的。在这篇博客中,我将详细分享这段迭代实现的历程,包括如何实现继承与多态,工厂模式的实现、如何解决代码中存在的各种问题、以及在整个过程中学到并积累的宝贵经验。

二、设计与分析

第一次迭代

题目:

航空快递以速度快、安全性高成为急件或贵重物品的首选。本题目要求对航空货运管理系统进行类设计,具体说明参看说明文件。
OO第九周作业题目说明.pdf

输入格式:
按如下顺序分别输入客户信息、货物信息、航班信息以及订单信息。

客户编号
客户姓名
客户电话
客户地址
运送货物数量
[货物编号
货物名称
货物宽度
货物长度
货物高度
货物重量
]//[]内的内容输入次数取决于“运送货物数量”,输入不包含“[]”
航班号
航班起飞机场
航班降落机场
航班日期(格式为YYYY-MM-DD)
航班最大载重量
订单编号
订单日期(格式为YYYY-MM-DD)
发件人地址
发件人姓名
发件人电话
收件人地址
收件人姓名
收件人电话
输出格式:
如果订单中货物重量超过航班剩余载重量,程序输出The flight with flight number:航班号 has exceeded its load capacity and cannot carry the order. ,程序终止运行。
如果航班载重量可以承接该订单,输出如下:
客户:姓名(电话)订单信息如下:

航班号:
订单号:
订单日期:
发件人姓名:
发件人电话:
发件人地址:
收件人姓名:
收件人电话:
收件人地址:
订单总重量(kg):
微信支付金额:

货物明细如下:

明细编号 货物名称 计费重量 计费费率 应交运费
1 ...
2 ...
注:输出中实型数均保留1位小数。

输入样例:
在这里给出一组输入。例如:

10001
郭靖
13807911234
南昌航空大学
2
101
发电机
80
60
40
80
102
信号发生器
55
70
60
45
MU1234
昌北国际机场
大兴国际机场
2025-04-22
1000
900001
2025-04-22
南昌大学
洪七公
18907912325
北京大学
黄药师
13607912546
输出样例:
在这里给出相应的输出。例如:

客户:姓名(电话)订单信息如下:

航班号:MU1234
订单号:900001
订单日期:2025-04-22
发件人姓名:洪七公
发件人电话:18907912325
发件人地址:南昌大学
收件人姓名:黄药师
收件人电话:13607912546
收件人地址:北京大学
订单总重量(kg):125.0
微信支付金额:3350.0

货物明细如下:

明细编号 货物名称 计费重量 计费费率 应交运费
1 发电机 80.0 25.0 2000.0
2 信号发生器 45.0 30.0 1350.0
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
栈限制
8192 KB

SourceMonitor分析

image
以下是对图中各项指标的解释:
代码规模:代码共 354 行,包含 27 条语句。注释占比极低,仅 2.0% 。分支语句占比 18.5% ,方法调用语句有 10 条。
类与方法情况:统计显示类和接口数量为 2 。平均每个类有 1.00 个方法,每个方法平均有 8.50 条语句 。
方法复杂度:最复杂方法是 WechatPay.pay() ,位于 21 行,复杂度为 6 。最深代码块在 321 行,最大代码块深度为 3 ,平均代码块深度 1.85 ,平均复杂度 3.50 。WechatPay.pay() 方法具体为复杂度 6、含 17 条语句、最大深度 3 、有 9 次方法调用;WechatPay.WechatPay() 复杂度 1、0 条语句、最大深度 0 、0 次方法调用 。
代码块深度与语句分布:展示了不同代码块深度对应的语句数量分布情况,并用柱状图直观呈现。还通过雷达图展示了注释百分比、每个类的方法数、平均复杂度等多个维度指标。

设计类图

image

分析

主要类及其职责:
Payment(抽象类)
职责:定义支付行为的抽象接口。
方法:pay(double amount):抽象方法,具体支付逻辑由子类实现。
WechatPay(支付实现类)
职责:实现微信支付方式。
方法:pay(double amount):输出微信支付金额。
ALiPay(支付实现类)
职责:实现支付宝支付方式。
方法:pay(double amount):输出支付宝支付金额。
Product(商品类)
职责:存储商品信息,计算计费重量和运费。
方法:calculate():计算商品的计费重量(实际重量与体积重量取较大值)。
getRealWeight():获取计费重量。
getPrice():根据计费重量计算运费(不同重量区间有不同费率)。
OrderItem(订单条目类)
职责:关联商品与订单,记录商品数量。
方法:getItemSum():计算该条目(商品)的总金额(单价 × 数量)。
Order(订单类)
职责:管理订单基本信息和条目列表。
方法:getTotalSum():计算订单总金额(所有条目金额之和)。
Customer(客户类)
职责:管理客户信息和订单。
方法:addOrder():创建并添加新订单。
OrderManager(订单管理类)
职责:管理订单集合。
方法:addOrder():添加订单。
deleteOrder():删除订单
代码实现的流程:
数据输入:
通过 Scanner 读取用户输入的客户信息、商品信息、航班信息等。
商品计算:
每个 Product 对象计算自身的计费重(calculate())和运费(getPrice())。
订单构建:
将多个 Product 对象封装为 OrderItem,再组合成Order。
费用计算:
Order 汇总所有 OrderItem 的金额(getTotalSum())。
输出结果:
打印订单详情,包括商品明细、总重量、总运费等。

历程与心得

在拿到这个题目后,相比于第一次大作业,我感觉这个大作业的逻辑还是十分清晰明了的,上课的时候老师也跟我们简单的讲了一些,所以逻辑就更加清晰了。然后我就开始根据不同的类来实现相应的属性及功能。然后仔细了解题目后,发现题目里面还是有一些规则,比如要搞清实际重量和体积重量的关系和区分,还有费率的计算。一个货运订单可以运送多件货物,每件货物均需要根据重量及费率单独计费,还有这条规则,一开始没有注意到,但是这个规则的实现也没有很困难,通过ArrayList 用来存放一个订单里的多件货物,循环添加 Product 对象,然后每个 Product 构造时,用 calculate 方法单独算自己的计费重量、Product 的 getPrice 方法依据自身计费重量确定费率,算出各自运费,最后OrderItem 关联 Product ,订单算总金额时,遍历算出每件货物金额再累加,实现单独计费就能够实现了。但是写完之后我发现,这个题目中给的测试样例中没有对支付方式的输入,所以一开始写的微信支付类和支付宝类并没有用上

第二次迭代

题目

7-3 NCHU_航空货运管理系统(继承与多态)
分数 50
较难
作者 段喜龙
单位 南昌航空大学
航空快递以速度快、安全性高成为急件或贵重物品的首选。本题目要求对航空货运管理系统进行类设计,具体说明参看说明文件。
OO第十二周作业题目说明.pdf

输入格式:
按如下顺序分别输入客户信息、货物信息、航班信息以及订单信息。

客户类型[可输入项:Individual/Corporate]
客户编号
客户姓名
客户电话
客户地址
货物类型[可输入项:Normal/Expedite/Dangerous]
运送货物数量
[货物编号
货物名称
货物宽度
货物长度
货物高度
货物重量
]//[]内的内容输入次数取决于“运送货物数量”,输入不包含“[]”
航班号
航班起飞机场
航班降落机场
航班日期(格式为YYYY-MM-DD)
航班最大载重量
订单编号
订单日期(格式为YYYY-MM-DD)
发件人地址
发件人姓名
发件人电话
收件人地址
收件人姓名
收件人电话
支付方式[可输入项:Wechat/ALiPay/Cash]
输出格式:
如果订单中货物重量超过航班剩余载重量,程序输出The flight with flight number:航班号 has exceeded its load capacity and cannot carry the order. ,程序终止运行。
如果航班载重量可以承接该订单,输出如下:
客户:姓名(电话)订单信息如下:

航班号:
订单号:
订单日期:
发件人姓名:
发件人电话:
发件人地址:
收件人姓名:
收件人电话:
收件人地址:
订单总重量(kg):
[微信/支付宝/现金]支付金额:

货物明细如下:

明细编号 货物名称 计费重量 计费费率 应交运费
1 ...
2 ...
注:输出中实型数均保留1位小数。

输入样例:
在这里给出一组输入。例如:

Corporate
10001
郭靖
13807911234
南昌航空大学
Expedite
2
101
发电机
80
60
40
80
102
信号发生器
55
70
60
45
MU1234
昌北国际机场
大兴国际机场
2025-04-22
1000
900001
2025-04-22
南昌大学
洪七公
18907912325
北京大学
黄药师
13607912546
ALiPay
输出样例:
在这里给出相应的输出。例如:

客户:郭靖(13807911234)订单信息如下:

航班号:MU1234
订单号:900001
订单日期:2025-04-22
发件人姓名:洪七公
发件人电话:18907912325
发件人地址:南昌大学
收件人姓名:黄药师
收件人电话:13607912546
收件人地址:北京大学
订单总重量(kg):125.0
支付宝支付金额:4360.0

货物明细如下:

明细编号 货物名称 计费重量 计费费率 应交运费
1 发电机 80.0 40.0 3200.0
2 信号发生器 45.0 50.0 2250.0

SourceMonitor分析

image
代码规模
代码总行数为 508 行,包含 239 条语句。注释占比为 19.5% 。分支语句占比 16.7% ,方法调用语句有 68 条。
类与方法情况
类和接口数量统计为 16 个。平均每个类有 2.38 个方法,每个方法平均有 4.50 条语句。
方法复杂度
最复杂方法是 ProductFactory.createProduct() ,处于 134 行,复杂度为 5 。最深代码块在 164 行,最大代码块深度达 4 ,平均代码块深度 1.62 ,平均复杂度 1.47 。
ProductFactory.createProduct() 方法具体表现为复杂度 5、含 9 条语句、最大深度 4 、有 1 次方法调用;Main.main() 复杂度 1、含 17 条语句、最大深度 16 、有 16 次方法调用。
代码块深度与语句分布
展示了不同代码块深度对应的语句数量分布,通过柱状图直观呈现。同时利用雷达图展示注释百分比、每个类的方法数、平均复杂度等多维度指标。

设计类图

image

分析

类的分析
Payment(抽象类)
职责:定义支付行为的抽象规范,作为支付方式的抽象,为具体支付类提供统一接口。
特点:含有抽象方法 pay(double amount),强制子类实现具体支付逻辑,体现了面向对象的多态性,方便后续扩展新的支付方式。
WechatPay、ALiPay、CashPay(具体支付类)
职责:分别实现 Payment 抽象类,提供微信、支付宝、现金三种具体支付方式的功能。
特点:通过重写 pay(double amount) 方法,输出对应支付方式的支付金额信息。代码简洁明了,遵循了抽象类的设计规范。
Product(抽象类)
职责:封装商品的基本属性和通用操作,如计算计费重量等,同时定义获取费率的抽象方法,由具体商品子类实现。
特点:包含商品的类型、ID、名称、尺寸、重量等属性,以及计算计费重量(比较实际重量和体积重量取大值)和获取价格(根据费率计算)的方法。将商品相关的核心逻辑进行了初步封装。
NormalPro、DangerPro、UrgentPro(具体商品类)
职责:继承 Product 抽象类,实现不同类型商品(普通、危险、紧急)的费率计算规则。
特点:在构造方法中调用父类构造方法初始化商品基本信息,重写 getRate 方法,根据不同重量区间设置各自的费率标准,实现了商品费率的差异化计算。
ProductFactory(工厂类)
职责:根据输入的商品类型,创建对应的商品实例,实现商品对象的创建逻辑封装。
特点:通过 createProduct 静态方法,使用 switch - case 语句判断商品类型,返回相应的商品对象,提高了代码的可维护性和扩展性,遵循了工厂设计模式。
OrderItem(订单条目类)
职责:表示订单中的单个商品条目,关联商品和数量,计算该条目金额。
特点:包含订单条目 ID、商品数量、关联商品等属性,通过 getItemSum 方法计算该条目商品的总金额(商品价格 × 数量),是订单金额计算的基础单元。
Order(订单类)
职责:管理订单的整体信息,包括关联客户、订单日期、订单 ID、订单条目列表和支付方式,计算订单总金额和最终金额(考虑客户折扣)。
特点:通过构造方法初始化订单相关信息,提供方法获取支付方式、计算订单总金额(汇总所有订单条目金额)以及最终金额(总金额 × 客户折扣率),实现了订单信息的集中管理和金额计算逻辑。
Customer(抽象类)
职责:定义客户的基本属性和行为规范,管理客户相关信息和订单操作。
特点:包含客户类型、ID、姓名等属性,以及添加订单的方法 addOrder,并定义抽象方法 getDiscountRate 供子类实现,体现了客户类型的差异化折扣策略。
PersonCus、EnterpriseCus(具体客户类)
职责:继承 Customer 抽象类,分别实现个人客户和企业客户的折扣率计算规则。
特点:在构造方法中调用父类构造方法初始化客户信息,重写 getDiscountRate 方法,分别返回个人客户(0.9 折)和企业客户(0.8 折)的折扣率。
CustomerFactory(工厂类)
职责:根据输入的客户类型,创建对应的客户实例,实现客户对象的创建逻辑封装。
特点:通过 createCustomer 静态方法,使用 switch - case 语句判断客户类型,返回相应的客户对象,遵循工厂设计模式,方便客户对象的创建和管理。
OrderManager(订单管理类)
职责:负责订单集合的管理,包括添加订单和删除订单操作。
特点:使用 LinkedList 存储订单,提供 addOrder 和 deleteOrder 方法对订单进行增删操作,实现了订单的基本管理功能。
PaymentFactory(工厂类)
职责:根据输入的支付类型,创建对应的支付方式实例,实现支付方式对象的创建逻辑封装。
特点:通过 createPayment 静态方法,使用 switch - case 语句判断支付类型,返回相应的支付方式对象,遵循工厂设计模式,便于支付方式的创建和扩展。
Main(主类)
职责:作为程序的入口,负责读取用户输入的各类信息,创建相关对象,构建订单并输出订单信息。
特点:通过 Scanner 从控制台读取客户、商品、航班、订单等信息,利用各个工厂类创建对象,组装成订单,并进行载重检查、金额计算和信息展示,是整个业务流程的控制中心。
方法的分析
抽象类方法
Payment.pay(double amount):抽象方法,定义支付行为规范,强制子类实现具体支付逻辑,是支付功能实现的核心抽象接口。
Product.getRate():抽象方法,用于获取商品的费率,由具体商品子类根据商品类型实现不同的费率计算规则,是商品运费计算的关键抽象方法。
Customer.getDiscountRate():抽象方法,用于获取客户的折扣率,由具体客户子类根据客户类型实现不同的折扣率计算规则,体现了客户折扣的差异化策略。
具体类方法
WechatPay.pay(double amount)、ALiPay.pay(double amount)、CashPay.pay(double amount):重写 Payment 抽象类的 pay 方法,实现各自支付方式的金额输出功能,方法逻辑简单直接。
NormalPro.getRate()、DangerPro.getRate()、UrgentPro.getRate():重写 Product 抽象类的 getRate 方法,根据不同商品类型和重量区间,实现具体的费率计算规则,逻辑清晰。
PersonCus.getDiscountRate()、EnterpriseCus.getDiscountRate():重写 Customer 抽象类的 getDiscountRate 方法,分别返回个人客户和企业客户的折扣率,实现客户折扣的差异化。
ProductFactory.createProduct(String productType,int productID,String name,double width,double length,double height,double weight):静态方法,根据商品类型创建对应的商品实例,使用 switch - case 语句进行类型判断,逻辑简洁明了,便于扩展新的商品类型。
CustomerFactory.createCustomer(String customerType,int customerID,String name):静态方法,根据客户类型创建对应的客户实例,通过 switch - case 实现类型判断和对象创建,遵循工厂模式,方便客户管理。
PaymentFactory.createPayment(String paymentType):静态方法,根据支付类型创建对应的支付方式实例,利用 switch - case 语句进行判断,方便支付方式的扩展和使用。
Order.getTotalSum():计算订单总金额,通过遍历订单条目列表,累加每个条目的金额,是订单金额计算的重要方法。
Order.getFinalPrice():根据订单总金额和客户折扣率计算最终金额,体现了客户折扣在订单金额计算中的应用。
OrderItem.getItemSum():计算订单条目中商品的总金额,通过商品价格乘以数量得出,是订单总金额计算的基础。
Customer.addOrder(LocalDate date,String orderID,ArrayListitems,Payment paymentWay):创建并添加新订单,将订单信息与客户关联,并通过订单管理器进行订单添加操作,实现了客户与订单的关联管理。

历程与心得

完成第一次迭代的时候发现抽象出来的支付类没有用到的地方,但这次的迭代增加了它的用地。然后这次的迭代还增加了货物(普通货物、危险货物和加急货物)和客户的类型(个人用户和集团用户)。看到这个改变之后,我就把产品类和客户类改成了抽象类,又分别增加了它们对应的子类。但是完成这一步之后,我有些迷茫了,不知道在主函数里面该怎么根据对应的输入进行创建对象。一开始我想着不行的话,就用if-else或者switch语句来对应创建。但是这样的话,如果类型增多,这部分就会违反开闭原则,而且后续如果要多个模块创建同类对象时,if-else 逻辑会重复出现在各处,导致代码冗余。并且也违反了单一变量原则。就在我犹豫的时候,突然想起来了老师上课刚教给我们的工厂模式,工厂类专注于对象创建,所以Main函数只需要调用工厂方法,不需要关心具体创建逻辑,也能够很好的符合开闭原则,便于扩展。

三、踩坑心得

第一次迭代的时候,一开始我只创建了order这一个类,忽略了一个订单上会有多个产品的规则,但在老师上课的提醒之下,回来对自己的代码进行了修改。所以每次开始写大作业前,还是要先把题目理解清楚,仔细分析好对应的类和方法之后再写。
第二次迭代,因为增加了支付方式这个属性,所以一开始写完这个类之后,没有在订单里面加上相应的属性,对应的构造方法也忘记修改,导致出现了一系列错误。
第二次代码实现的时候,因为新增加了一个支付类型的输入,所以程序从输入流读取数据的时候出现了问题
Exception in thread "main" java.util.NoSuchElementException: No line found
at java.base/java.util.Scanner.nextLine(Scanner.java:1651)
at Main.main(Main.java:296)
显示了这个错误,后来请教了同学,统一改成了Integer.parseInt(sc.nextLine());这种,就没有错误了,自己下次写的时候要注意这个问题,避免因换行符残留导致的输入错位问题。

四、改进建议

1.我的代码中的主函数中的输出部分过于繁琐,应该单独创建个类方法,来格式化输出订单信息。这样可以保证单一变量原则,同时也能提高代码的可读性。
2.我的一些类,方法的名字可能不具有可读性比如PersonCus、NormalCus等等。下次也会相应的改正。

五、总结

这次的大作业也让我熟悉了非常多知识的应用。这次大作业实现了客户管理、商品计费、订单处理及支付等核心功能。通过两次迭代优化,我逐步引入了工厂模式、继承与多态、抽象与接口等知识,解决了代码扩展性不足等问题。通过这次大作业,我更加熟悉了继承与多态的使用及好处,也让我看到了简单工厂模式的优势。

posted @ 2025-05-21 16:39  诺一王  阅读(28)  评论(0)    收藏  举报