第二次blog:航空货运管理系统
一、前言
第八次题目集的航空货运管理系统主要考求对航空货运管理系统进行类设计,第九次题目集就在第八次题目集的基础上增加了继承和多态的使用,两次题目集都重点考核面向对象设计原则中的单一职责原则、里氏代换原则、开 闭原则以及合成复用原则,要求我们能够熟练掌握并运用,整体的题目阅读量不算大,2次题目集的对逻辑算法的要求不高,难度不算大,就看对类的设计掌握的如何,整体来说这两次题目集对我们在类方面的设计起到了一个很好的锻炼作用。
二、设计与分析
1、题目要求:
<1>、题目集8:
航空快递以速度快、安全性高成为急件或贵重物品的首选。本题目要求对航空货运管理系统进行类设计,
输入格式:
按如下顺序分别输入客户信息、货物信息、航班信息以及订单信息。
客户类型[可输入项: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位小数。
计费重量的确定 空运以实际重量(GrossWeight)和体积重量(VolumeWeight)中的较 高者作为计费重量。 计算公式: 体积重量(kg)= 货物体积(长×宽×高,单位:厘米)÷6000 示例: 若货物实际重量为80kg,体积为120cm×80cm×60cm,则:体积重量 =(120×80×60)÷6000=96kg 计费重量取96kg(因96kg>80kg)。
基础运费计算 费率(Rate):航空公司或货代根据航线、货物类型、市场行情等制定(如 CNY 30/kg)。本次作业费率采用分段计算方式:
<2>、题目集9:
在题目集8的基础上费率计算做了更改:
费率(Rate):航空公司或货代根据航线、货物类型、市场行情等制定(如 CNY30/kg)。本次作业费率与货物类型有关,货物类型分为普通货物、危险货 物和加急货物三种,其费率分别为:
计算公式:基础运费 = 计费重量 × 费率 × 折扣率;
同时添加了相关的支付方式的输出。
2、代码分析:
<1>、题目集8的类图设计及代码分析如图:
(1)、代码分析:
· 分支语句:7.3% ,说明代码中分支逻辑(如 if - else
、switch
等)不算特别多
· 平均类方法:4.80 ,意味着每个类中平均有近 5 个方法
· 每个方法平均语句数:1.56 ,方法内平均语句较少,方法相对较简洁
· 最复杂方法的行数:5 ,且最复杂方法是 main()
,整体方法长度控制得较好
· 最大块深度:4 ,表示代码中嵌套结构最深为 4 层,嵌套不算过深,代码可读性相对 较好
· 平均块深度:1.82 ,平均嵌套程度较低
· 平均复杂度:1.27 ,代码整体复杂度处于较低水平
· 注释情况:带注释的只有3.0%,注释比较的少
· 平均类方法数:4.8,接近理想值(4-6),表明类的职责划分基本合理
整体上看代码的复杂度不算高,逻辑较为清晰,类与方法数量平衡,职责的划分是相对合理的
(2)、类图分析:
图中可以看出类有不同职责的划分,Goods
类负责商品信息的封装,Money
类处理与金额相关逻辑 ,DecideWeight
和 BillingRate
及其实现类分别负责重量计算和计费规则计算,使用了接口(如 DecideWeight
、BillingRate
)和实现类(DefaultDecideWeight
、DefaultBillingRate
)的形式,便于后期扩展和维护,符合面向对象的多态特性。
其中GoodsList
包含多个 Goods
对象,负责商品集合的管理为组合关系;PrintGoods
依赖多个类获取商品信息并展示,与商品信息为依赖关系;Money
调用 DecideWeight
和 BillingRate
的实现类计算最终价格,与他们为依赖关系;Goods
包含 Money
对象,二者属于聚合关系;Goods
通过 DecideWeight
和 BillingRate
接口计算重量和价格,和他们属于依赖关系。
从类图上看出基本上算是符合五大原则(单一职责原则、里氏代换原则、开闭原则、 合成复用原则、依赖倒转原则),但有些地方违反了这五大原则
<2>、题目集9的类图设计及代码分析如图:
(1)、代码分析:
· 分支语句占比:10.9% ,表明代码中存在一定的条件判断逻辑,但并不算复杂
· 方法调用:方法调用次数为 64 次,说明代码中各方法间存在一定的交互协作,体现了模块化编程思想
· 复杂度指标:最大复杂度为 22(BillingRate.Calculate()
方法 ),平均复杂度 1.91 ,大部分方法复杂度较低,但存在个别复杂度较高的方法。最大块深度为 4,平均块深度 2.00 ,意味着代码中存在一定程度的嵌套结构,不过整体嵌套深度不算过深
· 简单方法:多数方法复杂度为 1,语句较少,结构简单,说明代码在一定程度上遵循了单一职责原则
· 注释:仅 2.6% 的注释比例,注释严重不足
整体上看代码整体还可以,但是缺少一些注释,还有方法的最大复杂度较高
(2)、类图分析:
Goods类:用于封装商品的基本属性,如 goodsID
、goodsName
、width
、length
、height
、weight
;AddGoods类:通过 ArrayList
来存储 Goods
对象;Default类:处理与商品集合相关的一些操作;Money类:涉及与金额计算相关的逻辑,实现根据商品重量和计费规则计算金额的功能;PrintGoods
类:输出商品信息;
Money
类依赖 DecideWeight
和 BillingRate
接口;PrintGoods
类依赖 Goods
、DecideWeight
、Money
、BillingRate
等类;AddGoods
类与 Goods
类存在关联,通过管理 Goods
对象的列表来实现商品添加等操作;Default
类与 Goods
类关联;
代码中的大部分类的职责相对单一,体现出了面向对象的封装特性,组合等。五大原则的实现除了里氏代换原则其他的都有一点体现,但是在Money类里面承受了过多的职责,需要再继续拆分一下。
三、踩坑心得
1、我本来是像通过这样的方式将商品信息请求入列的
但是出现了这种错误:
因为 AddGoods
类中定义的 addList
方法参数是 (String goodsID,String goodsName,double width,double length,double height,double weight)
,而调用时传入的是一个 Goods
对象,方法参数类型不匹配,所以编译器报错,所以要在addList()类传参时用Goods goods的形式
2、开始在测试样例那测试时答案的结果是一样的,但是提交时还是出现了答案错误的提示:
后面发现是需要在这些后面加input.nextLine()消耗换行符
四、改进建议
1、代码里面的注释很少,对之后如果要做修改的话就十分的不友好,所以在之后写代码的时候要多增加注释,方便复盘自己的代码
2、在我的代码中,Money
直接依赖具体的 DecideWeight
和 BillingRate
实现类,可以在添加一个像Agent这样的类作为中间管理,减少其与其他代码的依赖性,方便后续的代码进行修改
3、PrintGoods类承载了很多的方法,违反了单一职责的原则,不仅仅有输出还有一些逻辑的处理,需要把他们再拆分一下;Money类包含复杂计算逻辑也存在违反单一职责原则的情况,也需要再对类进行拆分
4、在题目集9里面,题目添加了支付方式的输入,我是将其放在了Main里面,然后用if-else循环进行输出的,在这里为了更好的符合五大原则还可以将支付方式单独提出来作为一个类
5、还可以在代码里面添加一个用户类,负责货物与客户之间的联系
五、总结
在这两次题目集训练当中,重点对我们的类的设计还有面向对象设计的五大原则进行了训练。单一职责原则促使我将不同功能模块拆分至单独的类中,比如 Goods
类专门负责封装货物信息,BillingRate
类专注于计费费率计算,使得每个类的职责清晰明确,代码的可读性和可维护性大大提高。里氏代换原则要求子类能够替换父类,在设计涉及继承或接口实现的类时,保证了代码的稳定性和可扩展性。开闭原则通过接口和抽象类的使用得以体现,例如定义 DecideWeight
接口来确定计费重量,后续若有新的重量计算方式,只需新增实现类而无需修改现有代码,增强了代码的灵活性。合成复用原则中我学会了优先使用组合而非继承来实现代码复用,像 Money
类通过组合 DecideWeight
和 BillingRate
接口对象来计算应交运费,降低了类之间的耦合度。依赖倒转原则则强调高层模块依赖抽象接口,在代码中表现为各个业务类依赖于相关的接口,而非具体实现类,使得系统结构更加稳定。整体上看,通过这次对五大原则的实践运用,我更好的理解了五大原则的使用方法及其使用场景。
在这次的代码里面题目没有给我们类的设计,要求我们自己去设计,刚开始时是有点不知所措的,不知道该怎么自己去设计,就去仿造了一下前几次题目集中的那个电梯的类的设计,然后就掌握了一点设计技巧还有对那五大原则的学习就更好的帮我拆分类,根据题目要求将每个大要求拆分成一个一个的小要求然后就划分成一个类。
但同时这次训练也反映出了我对类的设计还有五大原则的掌握还是不够的,在今后的学习里面还要对这两方面多加学习,增强训练,可以根据书本上的例题自己先去想想该怎么去设计类,然后再和参考类设计进行对比,之后不断的优化一下代码,从而达到很好的锻炼目的。