面向对象程序设计 题目集8-9总结Blog
一、前言
两次题目集主要是考察类的设计以及继承和多态,没有设计到什么算法。刚开始接触时我还是比较害怕去写,怕像前一次的大作业非常难,但是从我接触了开始写了以后,发现确实和老师说的一样,并没有想象的那么难。这次大作业的核心就是设计好类与类之间的关系,并遵循单一职责原则、里氏代换原则、开闭原则、合成复用原则、依赖倒转原则等设计原则。例外,这次遇到的错误几乎都是语法错误,相比之前的算法错误改起来快很多,两次作业完成的时间也比之前短很多。
通过这两次题目集我也巩固并学习到了很多知识点
1、更加清楚应用了解面向对象设计原则中的单一职责原则、里氏代换原则、开闭原则以及合成复用原则、依赖倒转原则,知道了写代码时应用这些原则的好处
2、对于类与类之间的关系,特别是继承与多态有了更加清楚的认识,知道了如何实际地运用在代码上
3、在实现类与类之间的设计时也更加得心应手
4、知道了如何利用LinkedList建立类中多个对象等
二、设计与分析
1.第八次题目集
题目
题目:
某航空公司“航空货运管理系统”中的空运费的计算涉及多个因素,通常包
括货物重量/体积、运输距离、附加费用、货物类型、客户类型以及市场供需等。
本次作业主要考虑货物重量/体积,以下是具体的计算方式和关键要点:
一、计费重量的确定
空运以实际重量(GrossWeight)和体积重量(VolumeWeight)中的较
高者作为计费重量。
计算公式:
体积重量(kg)= 货物体积(长×宽×高,单位:厘米)÷6000
示例:
若货物实际重量为80kg,体积为120cm×80cm×60cm,则:
体积重量 =(120×80×60)÷6000=96kg
计费重量取96kg(因96kg>80kg)。
二、基础运费计算
1
费率(Rate):航空公司或货代根据航线、货物类型、市场行情等制定(如
CNY30/kg)。本次作业费率与货物类型有关,货物类型分为普通货物、危险货
物和加急货物三种,其费率分别为:
计算公式:基础运费 = 计费重量 × 费率 × 折扣率
其中,折扣率是指不同的用户类型针对每个订单的运费可以享受相应的折扣,
在本题中,用户分为个人用户和集团用户,其中个人用户可享受订单运费的9
折优惠,集团用户可享受订单运费的8折优惠。
三、题目说明
本次题目模拟某客户到该航空公司办理一次货运业务的过程:
航空公司提供如下信息:
航班信息(航班号,航班起飞机场,航班降落机场,航班日期,航班最大载
重量)
2
客户填写货运订单并进行支付,需要提供如下信息:
客户信息(姓名,电话号码等)
货物信息(货物名称,货物包装长、宽、高尺寸,货物重量等)
运送信息(发件人姓名、电话、地址,收件人姓名、电话、地址,所选
航班号,订单日期)
支付方式(支付宝支付、微信支付、现金支付)
注:一个货运订单可以运送多件货物,每件货物均需要根据重量及费率单独
计费。
程序需要从键盘依次输入填写订单需要提供的信息,然后分别生成订单信
息报表及货物明细报表。
本次代码中的类及类间关系
一、类的列表
代码中定义了 8 个类,分别是:
1.Main(主类,包含程序入口 main 方2法)
2.Transportinformation(抽象基础类,封装运输相关基础信息)
3.Sender(发件人类,继承自 4.Transportinformation)
5.Consignee(收件人类,继承自 6.Transportinformation)
7.FlightInfo(航班信息类)
8.Goods(货物类)
9.Order(订单类)
10.Control(控制类,包含计算运费和总重量的静态方法)
11.Client(客户类)
二、类间关系详解
-
继承关系(Inheritance)
Sender 和 Consignee 继承自 Transportinformation -
组合关系(Composition)
Order 包含多个类的实例作为成员变量
Order 类组合了以下类的对象,形成 “整体 - 部分” 关系,部分对象(如 FlightInfo)的生命周期依赖于 Order -
依赖关系(Dependency)
Main 类依赖其他类完成输入输出和业务逻辑
Control 类依赖 Order 类进行计算 -
关联关系(Association)
Goods 类与自身无直接关联,但通过 Order 类间接关联
Client 类与 Order 类无直接代码关联,但业务上存在隐含关系
思考
本次题目集有了前几次的经验,写之前我现将老师发的题目以及要求仔细看了一遍,差不多大概有了思路知道要写什么之后再去画
类图,设计类与类之间的关系,最后才是写代码实现。总得来说相比之前的题目集,这次的作业难度更低,主要是类与类的设计,
几乎没有涉及到什么算法,因此压力也更低,完成的速度更快
代码分析

类图

收获
通过这次题目集,我明白了怎么创建一个类多个对象,以及如何利用LinkedList具体代码去实现,更加熟悉了多态的语法以及如何在
代码上实现。但是最后出现了一点小错误,一直非零返回导致我很苦恼,后面发现是创建新对象时和构造函数里的属性顺序不一致,并
且在读取信息是也弄错了顺序。这也警告我一定要认真读取题目信息,仔细对照,不得马虎
以下是利用LinkedList创建多个对象的代码实现
for (int i = 0; i < goodsCount;i++) {
String goodsId = sc.nextLine();
String goodsName = sc.nextLine();
double width = sc.nextDouble();
double length = sc.nextDouble();
double height = sc.nextDouble();
double weight = sc.nextDouble();
sc.nextLine(); // 消费残留换行符
Goods good=new Goods(goodsName, width, length, height, weight);
goodsList.add(good);
}
2.第九次题目集
题目
某航空公司“航空货运管理系统”中的空运费的计算涉及多个因素,通常包
括货物重量/体积、运输距离、附加费用、货物类型、客户类型以及市场供需等。
本次作业主要考虑货物重量/体积,以下是具体的计算方式和关键要点:
一、计费重量的确定
空运以实际重量(GrossWeight)和体积重量(VolumeWeight)中的较
高者作为计费重量。
计算公式:
体积重量(kg)= 货物体积(长×宽×高,单位:厘米)÷6000
示例:
若货物实际重量为80kg,体积为120cm×80cm×60cm,则:
体积重量 =(120×80×60)÷6000=96kg
计费重量取96kg(因96kg>80kg)。
二、基础运费计算
1
费率(Rate):航空公司或货代根据航线、货物类型、市场行情等制定(如
CNY30/kg)。本次作业费率与货物类型有关,货物类型分为普通货物、危险货
物和加急货物三种,其费率分别为:
计算公式:基础运费 = 计费重量 × 费率 × 折扣率
其中,折扣率是指不同的用户类型针对每个订单的运费可以享受相应的折扣,
在本题中,用户分为个人用户和集团用户,其中个人用户可享受订单运费的9
折优惠,集团用户可享受订单运费的8折优惠。
三、题目说明
本次题目模拟某客户到该航空公司办理一次货运业务的过程:
航空公司提供如下信息:
航班信息(航班号,航班起飞机场,航班降落机场,航班日期,航班最大载
重量)
2
客户填写货运订单并进行支付,需要提供如下信息:
客户信息(姓名,电话号码等)
货物信息(货物名称,货物包装长、宽、高尺寸,货物重量等)
运送信息(发件人姓名、电话、地址,收件人姓名、电话、地址,所选
航班号,订单日期)
支付方式(支付宝支付、微信支付、现金支付)
注:一个货运订单可以运送多件货物,每件货物均需要根据重量及费率单独
计费。
程序需要从键盘依次输入填写订单需要提供的信息,然后分别生成订单信
息报表及货物明细报表。
在第八次题目的基础上,本次作业费率与货物类型有关,货物类型分为普通货物、危险货物和加急货物三种。折扣率是指不同的用户类型
针对每个订单的运费可以享受相应的折扣,在本题中,用户分为个人用户和集团用户,其中个人用户可享受订单运费的9折优惠,集团用户
可享受订单运费的 8 折优惠。支付方式增加了(支付宝支付、微信支付、现金支付),
本次代码中的类及类间关系
一、类的分类及说明
- Main
程序入口类,负责读取用户输入、创建各业务对象(如客户、货物、订单等),并协调各组件完成流程控制。
2.Control
提供计算总重量(calculateTotalWeight)和总运费(calculateFreight)的方法,通过参数依OrderItem和Goods类。
3.Client
客户基类,封装客户通用属性(ID、姓名、电话、地址),作为个人客户和团体客户的父类。
4.Person
个人客户类,继承自Client
5.Group
团体客户类,继承自Client,未扩展新功能(代码中构造函数仅调用父类逻辑)。
6.Goods(抽象类)
货物基类,实现Ratable接口,定义货物通用属性(名称、尺寸、重量)和抽象方法Rate()(费率计算),具体实现由子类完成。getchargeableWeight()方法已实现,用于计算体积重量与实际重量的较大值。
7.Ordinarygoods
普通货物类,继承自Goods,重写Rate()方法
8.Urgentgoods
加急货物类,继承自Goods,重写Rate()方法
9.Dangergoods
危险货物类,继承自Goods,重写Rate()方法
10.Transportinformation(抽象类)
运输信息基类,实现Show接口,封装发件人 / 收件人通用属性(姓名、电话、地址)和抽象方法show(),用于展示信息。
11.Sender
发件人类,继承自Transportinformation,重写show()方法
12.Consignee
收件人类,继承自Transportinformation,重写show()方法
13.FlightInfo
航班信息类,封装航班号、起降机场、日期、最大载重量等属性,供订单关联。
14.OrderItem
订单明细类,持有货物列表(LinkedList)和订单号,用于存储具体货物信息。
15.Order
订单类
16.Payment(抽象类)
支付方式基类,实现Show2接口,定义抽象方法show2(double basicFreight),用于展示支付金额。
17.Wechat
微信支付类,继承自Payment,重写show2()方法
18.ALiPay
支付宝支付类,继承自Payment,重写show2()方法
19.Cash
现金支付类,继承自Payment,重写show2()方法
20.Ratable
货物计费接口,定义Rate()和getchargeableWeight()方法
21.Show
信息展示接口,定义show()方法
22.Show2
支付展示接口,定义show2(double basicFreight)方法
二、类间关系总结
-
继承与实现关系
继承:
Person和Group继承自Client,形成客户体系的层次结构。
Urgentgoods、Dangergoods、Ordinarygoods继承自抽象类Goods,实现不同货物的费率逻辑。
Sender和Consignee继承自抽象类Transportinformation,重写信息展示方法。
Wechat、ALiPay、Cash继承自抽象类Payment,实现不同支付方式的展示逻辑。
接口实现:
Goods实现Ratable接口,定义货物计费规则。
Transportinformation实现Show接口,定义信息展示规则。
Payment实现Show2接口,定义支付金额展示规则。 -
组合与依赖关系
组合:
Order类组合了FlightInfo、Sender、Consignee、Payment、Client、OrderItem等对象,形成订单的完整数据结构。
OrderItem组合了Goods列表,表示一个订单明细包含多个货物。
依赖:
Main类依赖所有其他类完成输入输出和业务逻辑(。
Control类通过参数依赖OrderItem和Goods类,计算逻辑基于货物信息。
Order类通过多态依赖Payment接口 -
多态应用
货物多态:Goods列表可存储Ordinarygoods、Urgentgoods等子类实例,调用Rate()时自动匹配子类逻辑。
支付多态:Payment引用可指向Wechat、ALiPay等实例,调用show2()时根据具体类型输出不同格式。
思考
这次题目集在上一次的基础上增加了扩展了客户类型(个人用户和集团用户),支付方式(支付宝,微信,现金),货物类型(普通货物,紧急货物,危险货物),
在上一次的代码的基础上增加子类,修改相应的方法,同时经过了老师的提醒我得知还需添加一个订单明细类让逻辑更加合理。这次实验是在上一次的代码中进行
修改完善,总得来说写得更加流畅,完成的时间也缩短了
代码分析

类图

收获
字符串的比较方法过了太久忘记了,通过这次实验查找资料又复习了一遍,但是,这次还是出现了读取顺序出错导致的非零返回,因为一个小问题浪费了许多时间,
找错误的时候也没有太仔细导致,以后在写代码是要看清楚题目要求,尽量减少马虎的小漏洞。同时在设计类以及类间关系时,要跟现实生活联系起来,在遵守单
一职责原则、里氏代换原则、开闭原则、合成复用原则、依赖倒转原则等设计原则时同时也要考虑代码的合理性与逻辑性。
以下是多态以及字符串比较的代码
String Pay=sc.nextLine();
Payment pay=null;
if ("Wechat".equals(Pay)) {
pay = new Wechat();
} else if ("ALiPay".equals(Pay)) {
pay = new ALiPay();
} else if ("Cash".equals(Pay)) {
pay = new Cash();
}
String type2=sc.next();
int goodsCount = sc.nextInt();
sc.nextLine(); // 消耗换行符
LinkedList<Goods> goodsList = new LinkedList<>();
for (int i = 0; i < goodsCount;i++) {
Goods good=null;
String id=sc.nextLine();
String goodsName = sc.nextLine();
double width = sc.nextDouble();
double length = sc.nextDouble();
double height = sc.nextDouble();
double weight = sc.nextDouble();
sc.nextLine(); // 消耗残留换行符
if ("Normal".equals(type2)) {
good = new Ordinarygoods(goodsName, height, length, width, weight); // 修正参数顺序
} else if ("Expedite".equals(type2)) {
good = new Urgentgoods(goodsName, height, length, width, weight);
} else if ("Dangerous".equals(type2)) {
good = new Dangergoods(goodsName, height, length, width, weight); // 修正构造函数
}
goodsList.add(good);
}
以下是新添加的订单明细类
class OrderItem {
private String OrderId;
LinkedList<Goods> list=new LinkedList<>();
public OrderItem() {
super();
// TODO Auto-generated constructor stub
}
public OrderItem(String orderId, LinkedList<Goods> list) {
super();
OrderId = orderId;
this.list = list;
}
public String getOrderId() {
return OrderId;
}
public void setOrderId(String orderId) {
OrderId = orderId;
}
public LinkedList<Goods> getList() {
return list;
}
public void setList(LinkedList<Goods> list) {
this.list = list;
}
}
三、踩坑心得
1.在创建新对象时属性的顺序要和构造函数中的顺序保持一致
例如在创建对象时
String type2=sc.next();
int goodsCount = sc.nextInt();
sc.nextLine(); // 消耗换行符
LinkedList<Goods> goodsList = new LinkedList<>();
for (int i = 0; i < goodsCount;i++) {
Goods good=null;
String id=sc.nextLine();
String goodsName = sc.nextLine();
double width = sc.nextDouble();
double length = sc.nextDouble();
double height = sc.nextDouble();
double weight = sc.nextDouble();
sc.nextLine(); // 消耗残留换行符
if ("Normal".equals(type2)) {
good = new Ordinarygoods(goodsName, height, length, width, weight); // 修正参数顺序
} else if ("Expedite".equals(type2)) {
good = new Urgentgoods(goodsName, height, length, width, weight);
} else if ("Dangerous".equals(type2)) {
good = new Dangergoods(goodsName, height, length, width, weight); // 修正构造函数
}
goodsList.add(good);
}
构造函数为
public Goods(String name, double height, double length, double width, double weight) {
super();
Name = name;
this.height = height;
this.length = length;
this.width = width;
this.weight = weight;
}
2.遍历LinkedList的方法 goodsList为链表的名字
for (Goods good : goodsList) {
double chargeableWeight = good.getchargeableWeight();
double rate = good.Rate();
double freight = chargeableWeight * rate;
System.out.printf("%d %s %.1f %.1f %.1f\n", index, good.getName(), chargeableWeight, rate, freight);
index++;
}
四、修改建议
1.字符串比较优化
最好不要使用 == 比较字符串,(如 if ("Normal".equals(type2)) 更安全,避免空指针异常)。
2.增加代码的规范与可读性:统一命名、添加注释,提升维护效率等。
3.Control 类创建实例(Control control=new Control();),但其方法为静态,可直接调用静态方法。
修改:删除实例化代码,直接调用 Control.calculateTotalWeight(orderitem);
4.type1 的取值为 Individual 和 Corporate,但代码中 switch 直接匹配字符串,可以使用枚举类型提高安全性。
如
enum ClientType {
Individual, Corporate
}
5.OrderItem 仅存储订单号和货物列表,可将货物相关计算(如总重量、总运费)移至 OrderItem 中,减轻 Control 类的职责。
五、总结
一开始我并不是很清楚那些规则的作用与好处,以及为什么要遵循那些规则,对那些概念的理解停留在字面层面,并不清楚这些规则如何具体作用于代码,但是通过
这两次题目集的迭代,使我更加清楚单一职责原则、里氏代换原则、开闭原则、合成复用原则、依赖倒转原则等设计原则的作业以及这样做的好处。在设计类与类之间
的关系,包括继承、依赖、组合等也有了更好的感觉,对于是否要用继承,继承些什么,也有了进一步的了解。要在完成这次题目集时,逐渐有了“面向抽象设计” 的
思维。还是要多写注释,这样不仅别人看得清楚易懂,自己在之后看了也会更快想起代码内容,在这次第二次作业时就有点忘记了第一次是怎么写的了。在之后的代码
中我也会遵循“面向抽象设计” 的思维,让代码具有复用性,可以应对不同的需求。
我感觉这两次的题目集难度刚刚好,没有上一次那么难,但是做起来也需要时间。感觉老师对面向对象的设计原则也讲解得比较深刻,但是要是能再细一点是最好不过
了。我会深刻吸取这次出现的错误,复习那些不熟悉的语法,在以后一步步做得更好更完善。
浙公网安备 33010602011771号