面向对象程序设计课程总结性Blog
一、前言
面向对象程序设计课程作为计算机专业的核心课程,通过一系列具有挑战性的学习任务,系统地引导我们掌握面向对象编程的核心思想与实践技能。整个课程涵盖了Blog作业、PTA编程作业、实验项目以及线上线下课程学习,形成了理论与实践紧密结合的教学体系。
从工作量来看,课程任务呈现出逐步递增的特点。初期的PTA作业聚焦于基础语法和简单类的设计,每个题目代码量约在100-200行,每周2-3题的题量能够让我们稳步夯实基础。随着课程深入,题目集8和题目集9的难度显著提升,如航空货运管理系统的设计与实现,代码量突破1000行,需要综合运用继承、多态、容器等多种技术,配套的Blog作业要求对设计进行深度分析,每次撰写需投入8-10小时进行代码复盘与文档整理。实验环节则注重团队协作,从电梯调度系统的单人实现到多电梯协同调度的小组项目,代码规模扩展至2000行以上,需要多次进行代码审查与迭代优化。
课程难度呈现出阶梯式上升的特点。前期围绕类的封装与基本接口设计,如点线面几何对象的继承体系构建,难度适中,重点在于理解类的属性与方法设计。中期题目集8引入“继承与多态”“容器类操作”等核心知识点,如航空货运系统基础版的类设计,需要考虑客户、订单、货物、航班等多实体间的关联关系,UML类图的绘制与代码实现同步进行,难度开始提升。题目集9进一步深化系统复杂度,在航空货运系统中引入客户类型、货物类型和支付方式等多维度业务逻辑,同时电梯调度系统从单电梯控制扩展到多电梯协同,涉及请求队列管理、方向控制、负载均衡等复杂逻辑,对系统设计能力提出了更高要求。
线上课程通过慕课平台提供了丰富的理论资源,从面向对象的基本概念到设计模式的应用,每个知识点配套有案例讲解与习题。线下课程则注重互动式学习,通过课堂讨论、代码演示和问题答疑,帮助我们解决实践中遇到的具体问题。整体而言,课程通过“理论学习-编程实践-设计分析-文档总结”的闭环教学模式,使我们在掌握面向对象技术的同时,培养了系统设计思维和问题解决能力。
二、面向对象技术总结
(一)封装的理解与应用
封装作为面向对象的基础特性,在课程学习中贯穿始终。通过将数据与操作数据的方法封装在类中,实现了信息隐藏和模块化设计。在航空货运管理系统中,Customer类封装了客户的基本信息(客户编号、姓名、电话、地址),通过getter和setter方法控制属性的访问,避免了外部对内部状态的直接修改。例如:
点击查看代码
public class Customer {
private String customerId;
private String name;
private String phone;
private String address;
public Customer(String customerId, String name, String phone, String address) {
this.customerId = customerId;
this.name = name;
this.phone = phone;
this.address = address;
}
public String getDisplayInfo() {
return "客户编号:" + customerId + ", 姓名:" + name + ", 电话:" + phone + ", 地址:" + address;
}
}
在实践中,我深刻体会到封装的重要性:合理的封装能够降低类之间的耦合度,提高代码的可维护性。例如,在电梯调度系统中,Elevator类封装了电梯的状态(当前楼层、运行方向、请求队列)和操作(开门、关门、运行),外部只能通过定义好的接口与电梯交互,避免了状态的不一致性。然而,在初期设计中,我曾将过多的业务逻辑放在Main类中,导致Main类职责过重,违反了封装的原则。通过重构,将输入处理、业务逻辑和输出分离到不同的类中,显著提升了代码的可读性和可维护性。
(二)继承与多态的深入实践
继承与多态是面向对象编程的核心概念,也是课程的重点和难点。在题目集8和9的航空货运系统中,通过继承实现了客户类型和货物类型的扩展。定义Customer基类,派生出IndividualCustomer(个人客户)和CorporateCustomer(企业客户),未来可进一步扩展客户等级等属性。同样,定义Cargo抽象类,派生出NormalCargo(普通货物)、ExpediteCargo(加急货物)、DangerousCargo(危险货物),通过多态实现不同类型货物的费率计算。
点击查看代码
abstract class Cargo {
protected String id;
protected String name;
protected double width, length, height, weight;
protected double chargeWeight, rate, fee;
// 抽象方法,由子类实现
abstract double calculateRate();
public void calculateChargeWeight() {
double volume = width * length * height;
double volumeWeight = volume / 6000;
chargeWeight = Math.max(weight, volumeWeight);
}
public void calculateFee() {
fee = chargeWeight * calculateRate();
}
}
// 加急货物子类
class ExpediteCargo extends Cargo {
@Override
public double calculateRate() {
// 加急货物费率上浮20%
if (chargeWeight < 20) return 35 * 1.2;
else if (chargeWeight < 50) return 30 * 1.2;
else if (chargeWeight < 100) return 25 * 1.2;
else return 15 * 1.2;
}
}
通过多态的应用,当新增货物类型时,只需创建新的子类并实现相应的方法,无需修改现有代码,符合开闭原则。在电梯调度系统中,多态体现在请求处理上,将InsideRequest和OutsideRequest统一视为Request对象,通过父类引用调用子类方法,实现了请求处理的统一接口。
然而,在多态的使用中也存在理解不深入的问题。例如,在题目集9中使用ArrayList存储点、线、面对象时,误将子类方法的返回类型定义不一致,导致运行时异常。通过查阅资料和调试,深刻理解了里氏替换原则的重要性,确保子类能够完全替代父类而不影响程序的正确性。
(三)抽象类与接口的设计
抽象类和接口是实现多态的重要手段,在课程中多次应用于系统设计。抽象类用于定义具有共同属性和方法的基类,如Cargo抽象类定义了货物的基本属性和计算方法,由子类具体实现。接口则用于定义行为契约,如在航空货运系统中,定义RateCalculator接口用于费率计算,通过策略模式实现不同的费率策略。
// 费率计算策略接口
public interface RateCalculator {
double calculateRate(double chargeWeight);
}
// 普通货物费率策略
public class NormalRateCalculator implements RateCalculator {
@Override
public double calculateRate(double chargeWeight) {
if (chargeWeight < 20) return 35;
else if (chargeWeight < 50) return 30;
else if (chargeWeight < 100) return 25;
else return 15;
}
}
// 货物类注入策略
public class Cargo {
private RateCalculator rateCalculator;
public Cargo(RateCalculator rateCalculator) {
this.rateCalculator = rateCalculator;
}
public double getRate() {
return rateCalculator.calculateRate(chargeWeight);
}
}
在电梯调度系统中,接口的应用体现在控制器与电梯的交互上,定义ElevatorController接口规范调度逻辑,使不同的调度策略可以互换。通过抽象类和接口的使用,系统的扩展性得到了显著提升,但在初期设计中,常常混淆抽象类和接口的应用场景,例如在需要实现代码复用的地方使用了接口,而在需要定义默认行为的地方使用了抽象类。通过不断的实践和反思,逐渐掌握了两者的区别:抽象类适合有共同实现的场景,接口适合定义行为契约。
(四)集合框架的应用
Java集合框架在课程作业中被广泛使用,用于存储和管理对象。在航空货运系统中,使用ArrayList存储货物列表,LinkedHashMap存储订单明细,利用其插入顺序保持的特性,确保订单信息的正确展示。在电梯调度系统中,使用Queue接口封装请求队列,实现请求的入队和出队操作,提高了代码的可复用性。
点击查看代码
// 订单类使用LinkedHashMap存储货物
public class Order {
private String orderId;
private LinkedHashMap<Integer, Cargo> cargoList;
private double totalWeight;
private double totalFee;
public Order(String orderId) {
this.orderId = orderId;
this.cargoList = new LinkedHashMap<>();
}
public void addCargo(Cargo cargo) {
cargoList.put(cargo.getId(), cargo);
totalWeight += cargo.getChargeWeight();
totalFee += cargo.getFee();
}
public String getDisplayInfo() {
StringBuilder sb = new StringBuilder();
sb.append("订单编号:").append(orderId).append("\n");
sb.append("订单总重量:").append(totalWeight).append("kg\n");
sb.append("订单总费用:").append(totalFee).append("元\n");
sb.append("货物明细:\n");
cargoList.values().forEach(cargo -> sb.append(cargo.getDisplayInfo()).append("\n"));
return sb.toString();
}
}
在使用集合框架时,需要根据业务需求选择合适的数据结构。例如,频繁的插入操作适合使用ArrayList,而需要按插入顺序访问时LinkedHashMap更为合适。在电梯调度系统中,使用优先队列(PriorityQueue)对请求进行排序,根据请求的优先级决定电梯的停靠顺序,提高了调度效率。然而,在初期使用集合时,常常忽略泛型的使用,导致类型转换异常,通过强制使用泛型和类型检查,逐渐养成了良好的编程习惯。
(五)异常处理机制
异常处理是程序健壮性的重要保障,在课程作业中逐步引入异常处理机制。在航空货运系统中,对货物尺寸和重量进行非负数校验,在构造方法中抛出IllegalArgumentException,确保输入数据的合法性。
点击查看代码
public class Cargo {
public Cargo(String id, String name, double width, double length, double height, double weight) {
if (width <= 0 || length <= 0 || height <= 0 || weight <= 0) {
throw new IllegalArgumentException("货物尺寸和重量必须为正数");
}
this.id = id;
this.name = name;
this.width = width;
this.length = length;
this.height = height;
this.weight = weight;
}
// 其他方法...
}
在电梯调度系统中,使用try-catch捕获输入格式异常,避免程序因非法输入而崩溃。通过定义业务异常类(如InvalidInputException、OverloadException),区分系统异常和业务逻辑异常,使异常处理更加清晰。然而,在异常处理的实践中,存在异常捕获粒度不够精细的问题,有时会使用catch (Exception e)捕获所有异常,掩盖了潜在的问题。通过学习异常处理的最佳实践,逐渐掌握了根据异常类型进行针对性处理的方法,提高了程序的健壮性。
(六)JavaFX的应用与不足
课程中涉及了JavaFX的基础应用,用于实现简单的图形用户界面。在实验项目中,使用JavaFX构建了电梯调度系统的可视化界面,通过SceneBuilder设计界面布局,结合事件处理实现按钮点击、状态更新等交互功能。
点击查看代码
// JavaFX界面初始化
public class ElevatorGUI extends Application {
private Elevator elevator;
private Label currentFloorLabel;
private Button upButton, downButton, insideButton;
@Override
public void start(Stage primaryStage) {
// 初始化电梯
elevator = new Elevator(1, 20);
// 创建界面元素
currentFloorLabel = new Label("当前楼层:1");
upButton = new Button("上行请求");
downButton = new Button("下行请求");
insideButton = new Button("内部请求");
// 事件处理
upButton.setOnAction(e -> handleOutsideRequest(elevator.getCurrentFloor(), "UP"));
downButton.setOnAction(e -> handleOutsideRequest(elevator.getCurrentFloor(), "DOWN"));
insideButton.setOnAction(e -> handleInsideRequest());
// 布局设置
VBox vbox = new VBox(20, currentFloorLabel, upButton, downButton, insideButton);
vbox.setAlignment(Pos.CENTER);
vbox.setPadding(new Insets(50));
Scene scene = new Scene(vbox, 300, 400);
primaryStage.setTitle("电梯调度系统");
primaryStage.setScene(scene);
primaryStage.show();
// 启动电梯线程
new Thread(elevator).start();
}
// 其他方法...
}
通过JavaFX的学习,掌握了基本的界面设计和事件处理,但在复杂界面布局和动画效果实现方面还存在不足。例如,在多电梯协同调度的可视化中,无法实现电梯运行轨迹的平滑动画,界面交互效果较为简单。此外,JavaFX与业务逻辑的耦合度较高,没有很好地遵循MVC设计模式,导致代码维护困难。未来需要进一步学习JavaFX的高级特性,掌握界面与逻辑分离的设计方法。
(七)技术掌握的不足与改进方向
尽管通过课程学习掌握了面向对象的核心技术,但在以下方面仍存在不足:
-
设计模式的深度应用:目前仅停留在策略模式、工厂模式等基础模式的简单使用,对结构型模式(如装饰器、适配器)和行为型模式(如观察者、模板方法)的应用仍不熟练。在航空货运系统中,费率计算逻辑虽然使用了策略模式,但对于更复杂的业务场景,如货物类型和客户类型的组合优惠,未能灵活运用组合模式或责任链模式。
-
性能优化意识:在处理大量数据时,对数据结构的效率考虑不足。例如,在电梯调度系统中,使用LinkedList存储请求队列,导致频繁的删除操作效率低下,未考虑使用ArrayList或更高效的数据结构。此外,对于算法的优化,如电梯调度策略的改进,未能深入研究最短路径或负载均衡算法。
-
代码规范与文档编写:代码注释和类设计文档不够完善,在团队开发中可能导致沟通成本增加。例如,在航空货运系统中,核心类的方法缺乏详细的Javadoc注释,外部调用者难以快速理解方法的功能和参数要求。
-
测试驱动开发:单元测试的覆盖率较低,仅对核心功能进行了简单测试,未形成完整的测试体系。在电梯调度系统中,未对边界情况(如电梯满载、请求队列空)进行全面测试,导致潜在的bug未能及时发现。
针对以上不足,未来的改进方向包括:系统学习设计模式,通过阅读经典案例和实践项目加深理解;学习数据结构与算法,提高性能优化能力;养成良好的代码规范和文档编写习惯,使用工具生成API文档;掌握测试驱动开发方法,提高代码的可靠性。
三、采坑心得
(一)输入处理的边界陷阱
在课程作业中,输入处理是最常见的问题来源,多次因输入校验不严格导致程序异常。在题目集8的航空货运系统中,未对货物尺寸(宽、长、高)和重量进行非负数校验,当输入-5 3 4时,程序抛出NegativeArraySizeException,或者计算体积重量为负数,导致计费逻辑错误。
点击查看代码
// 初始错误代码(未校验)
public Cargo(String id, String name, double width, double length, double height, double weight) {
this.id = id;
this.name = name;
this.width = width;
this.length = length;
this.height = height;
this.weight = weight;
calculateChargeWeight();
}
解决过程:在构造方法中添加参数校验,对非法输入立即抛出异常,避免错误数据流入业务逻辑层。
点击查看代码
// 改进后代码(添加校验)
public Cargo(String id, String name, double width, double length, double height, double weight) {
if (width <= 0 || length <= 0 || height <= 0 || weight <= 0) {
throw new IllegalArgumentException("货物尺寸和重量必须为正数");
}
this.id = id;
this.name = name;
this.width = width;
this.length = length;
this.height = height;
this.weight = weight;
calculateChargeWeight();
}
教训:输入校验应遵循“早校验早报错”原则,在数据源头进行合法性检查,避免错误扩散。同时,异常信息应清晰明确,便于调试和用户理解。
(二)类职责混乱的维护困境
题目集8的Order类同时承担订单信息管理、货物费用计算和输出格式化功能,代码行数超过300行,导致可读性极差。当需要修改订单输出格式或新增计费规则时,需要修改同一类中的多个方法,增加了维护成本。
点击查看代码
// 职责混乱的Order类片段
public class Order {
private String orderId;
private Customer customer;
private Flight flight;
private List<Cargo> cargoList;
private double totalWeight;
private double totalFee;
// 订单信息管理方法
public void addCargo(Cargo cargo) {
cargoList.add(cargo);
totalWeight += cargo.getChargeWeight();
totalFee += cargo.getFee();
}
// 费用计算方法
public double calculateTotalFee() {
return cargoList.stream().mapToDouble(Cargo::getFee).sum();
}
// 输出格式化方法
public String getDisplayInfo() {
StringBuilder sb = new StringBuilder();
sb.append("客户:").append(customer.getName()).append("\n");
sb.append("订单总重量:").append(totalWeight).append("kg\n");
sb.append("货物明细:\n");
cargoList.forEach(cargo -> sb.append(cargo.getDisplayInfo()).append("\n"));
return sb.toString();
}
}
重构方案:提取OrderFormatter类负责订单信息的格式化输出,分离费用计算逻辑到CargoFeeCalculator接口,通过策略模式实现不同货物类型的费用计算。
点击查看代码
// 重构后Order类
public class Order {
private String orderId;
private Customer customer;
private Flight flight;
private List<Cargo> cargoList;
private double totalWeight;
private CargoFeeCalculator feeCalculator;
public Order(String orderId, Customer customer, Flight flight, CargoFeeCalculator feeCalculator) {
this.orderId = orderId;
this.customer = customer;
this.flight = flight;
this.cargoList = new ArrayList<>();
this.feeCalculator = feeCalculator;
}
public void addCargo(Cargo cargo) {
cargoList.add(cargo);
totalWeight += cargo.getChargeWeight();
}
public double calculateTotalFee() {
return cargoList.stream().mapToDouble(c -> feeCalculator.calculate(c)).sum();
}
}
点击查看代码
// 订单格式化类
public class OrderFormatter {
public static String format(Order order) {
StringBuilder sb = new StringBuilder();
sb.append("客户:").append(order.getCustomer().getName()).append("\n");
sb.append("订单总重量:").append(order.getTotalWeight()).append("kg\n");
sb.append("订单总费用:").append(order.calculateTotalFee()).append("元\n");
sb.append("货物明细:\n");
order.getCargoList().forEach(cargo -> sb.append(cargo.getDisplayInfo()).append("\n"));
return sb.toString();
}
}
效果:类职责单一化后,代码可维护性显著提升,新增计费规则时只需实现新的CargoFeeCalculator接口,修改输出格式时只需修改OrderFormatter类,无需改动Order类。
教训:类的设计应遵循单一职责原则,每个类只负责一项核心功能,降低类之间的耦合度,提高代码的可维护性和可扩展性。
(三)多态与容器结合的类型陷阱
在题目集9中,使用ArrayList
点击查看代码
// 错误的子类实现
class Plane extends Element {
@Override
public void display() { // 错误:返回类型应为String
System.out.println("The Plane's color is:" + color);
}
}
// 基类定义
abstract class Element {
protected String color;
abstract String display();
}
解决过程:确保所有子类正确重写父类方法,返回类型、参数列表完全一致,遵循里氏替换原则。
点击查看代码
// 正确的子类实现
class Plane extends Element {
@Override
public String display() {
return "The Plane's color is:" + color + ", area is:" + calculateArea();
}
}
教训:使用容器存储多态对象时,必须严格遵循里氏替换原则,子类方法必须与父类方法兼容,否则会导致运行时异常。在设计继承体系时,应仔细检查方法的签名,确保子类能够完全替代父类。
(四)代码规范与注释的缺失
在初期的代码中,存在大量命名不规范、注释缺失的问题,严重影响代码的可读性和可维护性。例如,Cargo类的构造方法名拼写错误为Carao(),方法名calculate ChargeWeight()包含空格,违反驼峰命名法;核心逻辑如货物计费重量计算未添加注释,导致理解困难。
点击查看代码
// 命名不规范且无注释的代码
public class Carao { // 错误:类名应为Cargo
private double width, length, height, weight;
private double chargeWeight;
public Carao(String id, String name, double width, double length, double height, double weight) {
this.width = width;
this.length = length;
this.height = height;
this.weight = weight;
}
public void calculate ChargeWeight() { // 错误:方法名含空格
double volumeWeight = width * length * height / 6000;
chargeWeight = Math.max(weight, volumeWeight);
}
}
改进措施:规范命名,使用驼峰命名法,确保见名知意;在关键逻辑处添加注释,解释代码的意图和实现方法。
// 改进后的代码
点击查看代码
public class Cargo {
private double width, length, height, weight;
private double chargeWeight;
public Cargo(String id, String name, double width, double length, double height, double weight) {
if (width <= 0 || length <= 0 || height <= 0 || weight <= 0) {
throw new IllegalArgumentException("货物尺寸和重量必须为正数");
}
this.width = width;
this.length = length;
this.height = height;
this.weight = weight;
}
/**
* 计算计费重量(取实际重量与体积重量的较大值)
* 体积重量计算公式:长×宽×高÷6000(单位:kg)
*/
public void calculateChargeWeight() {
double volumeWeight = width * length * height / 6000;
chargeWeight = Math.max(weight, volumeWeight);
}
}
教训:良好的代码规范和注释是团队协作的基础,能够显著降低沟通成本,提高开发效率。在编写代码时,应养成规范命名和及时注释的习惯,使用代码分析工具(如SourceMonitor)检查代码质量,避免出现“火星文”命名和无注释的情况。
四、改进建议及总结
(一)课程综合性总结
通过面向对象程序设计课程的学习,我在知识和技能上都取得了显著的提升。在知识层面,深入理解了面向对象的核心概念(封装、继承、多态),掌握了抽象类、接口、集合框架、异常处理等Java编程技术,了解了设计模式的基本思想和应用场景。在技能层面,通过完成一系列具有挑战性的作业和实验,培养了系统设计能力、问题解决能力和代码调试能力,能够从需求分析出发,设计合理的类结构,通过UML类图可视化设计,并实现复杂的业务逻辑。
从航空货运管理系统的基础版到扩展版,再到电梯调度系统的逐步优化,每一个项目都是对面向对象技术的综合应用。在航空货运系统中,通过继承实现客户和货物类型的扩展,使用策略模式优化费率计算逻辑,体会到设计模式如何提升代码的可扩展性;在电梯调度系统中,从单电梯控制到多电梯协同,通过合理的类设计和接口定义,实现了请求队列管理和调度策略的分离,理解了系统架构设计的重要性。
然而,课程学习也暴露出一些不足,如设计模式的深度应用不足、性能优化意识欠缺、代码规范和文档编写不够完善等。这些不足为未来的学习指明了方向,需要在后续的学习和实践中不断改进和提升。
(二)对课程的改进建议
1. 加强设计模式的案例教学
目前课程中设计模式的讲解较为基础,建议增加实际案例分析,帮助学生理解如何运用设计模式解决实际问题。例如,在航空货运系统中,除了策略模式,还可以引入工厂模式创建不同类型的客户和货物,使用观察者模式实现订单状态的实时通知。通过具体案例的讲解和实践,让学生掌握设计模式的应用场景和实现方法。
2. 深化性能优化与算法讲解
在面向对象编程中,性能优化是一个重要方面,但课程中涉及较少。建议增加数据结构与算法在面向对象系统中的应用内容,如不同集合框架的性能特点、电梯调度算法的优化(如LOOK算法、SCAN算法)、航空货运系统中的负载均衡算法等。通过理论讲解和实战练习,培养学生的性能优化意识和能力。
3. 强化代码规范与测试驱动开发
代码规范和测试是高质量代码的重要保障,建议在课程中增加相关内容。可以引入代码规范检查工具(如CheckStyle),要求学生在作业中遵循统一的代码规范;增加单元测试的教学内容,使用JUnit框架进行测试驱动开发,提高代码的可靠性。通过代码审查和测试报告,帮助学生养成良好的编程习惯。
4. 丰富实践项目的类型和难度
目前的实践项目主要集中在航空货运和电梯调度系统,建议增加更多类型的项目案例,如学生管理系统、图书管理系统、网络聊天系统等,涵盖不同的业务场景。同时,项目难度可以进一步分层,从单人完成的小型项目到小组协作的中型项目,逐步提升学生的系统设计和团队协作能力。
5. 加强线上线下教学的互动性
线上课程可以增加讨论区的活跃度,设置每周讨论主题,鼓励学生分享学习心得和遇到的问题;线下课程可以增加代码演示和现场调试环节,针对学生普遍存在的问题进行集中讲解。同时,建立课程学习小组,促进学生之间的交流和合作,形成良好的学习氛围。
(三)总结
面向对象程序设计课程是一门理论与实践紧密结合的课程,通过一系列精心设计的学习任务,引导学生逐步掌握面向对象编程的核心技术和思想。从基础的类设计到复杂的系统架构,从单一功能实现到多模块协同工作,每一次作业和实验都是一次挑战和成长的机会。
在课程学习中,我不仅掌握了Java编程的核心技术,更重要的是培养了面向对象的设计思维,学会了如何从现实问题中抽象出对象,定义对象之间的关系,并通过合理的设计模式和架构实现系统。同时,也深刻认识到代码质量的重要性,良好的代码规范、适当的注释和完善的测试是软件开发的基础。
未来,我将继续深入学习设计模式和软件架构,提高性能优化能力,加强代码规范和测试意识,将所学知识应用到更多的实践项目中,不断提升自己的编程水平和解决实际问题的能力。感谢课程提供的学习机会和挑战,这将是我计算机专业学习中重要的一步。
24201321-黄永铎