OO课程第四单元总结与全课程回顾
2026OO课程第四单元总结与全课程回顾:正向建模驱动下的架构演进之路
一、引言
2026年面向对象设计与构造课程已圆满落幕。回顾四个单元的学习历程,从基础的Java语法训练到复杂的系统设计,从手工编码到UML正向建模,每一阶段都是一次思维的跃迁。第四单元作为课程的收官之作,引入了正向建模与开发的全新范式,让我深刻认识到UML模型在现代软件工程中的核心价值。本文将从第四单元的实践出发,系统总结正向建模的实践经验、架构设计思维演进、测试思维蜕变以及整个课程的收获与感悟。
二、第四单元正向建模实践
2.1 正向建模与开发的核心内涵
正向建模(Forward Modeling)是指从抽象的UML模型出发,通过模型驱动的方式生成或指导代码实现的过程。与传统的"先写代码后画图"不同,正向建模强调"模型先行",要求开发者在编码之前完成系统的结构化设计,确保模型与代码之间建立严格的追踪关系。
在第四单元的三次作业中,我们分别完成了:
- 第一次作业:基于给定的UML类图实现图书馆管理系统的基础功能
- 第二次作业:扩展类图并实现顺序图(Sequence Diagram)的交互逻辑
- 第三次作业:完善状态图(Statechart Diagram)并实现状态驱动的业务逻辑
2.2 两阶类图在正向建模中的关键作用
第四单元最核心的设计理念是"两阶类图"(Two-Tier Class Diagram),即先构建概念模型(Conceptual Model),再细化为设计模型(Design Model)。这种分层建模策略在正向建模过程中发挥了至关重要的作用。
2.2.1 第一阶:概念模型——领域实体的抽象
概念模型关注"系统是什么",主要提取领域中的核心实体及其关系。在图书馆管理系统中,概念模型包含:
核心实体:
- Book(图书):抽象类,定义ISBN、类型、副本列表等属性
- BookCopy(图书副本):具体实例,追踪位置、借阅状态、历史记录
- User(用户):管理学分、借阅记录、预约状态
- Library(图书馆):协调所有实体,处理业务请求
- Location(位置枚举):BS/TBS/BRO/AO/RR/USER六种位置
概念模型的价值在于:
- 统一领域语言:通过UML类图明确实体命名和职责边界,避免开发过程中的语义歧义
- 识别核心关系:Library 1→* Book、Book 1→* BookCopy、User 1→* BookCopy等关联关系在模型层面确立
- 指导数据结构设计:概念模型直接映射到代码中的Map/List等数据结构选型
2.2.2 第二阶:设计模型——行为与交互的细化
设计模型关注"系统怎么做",在概念模型的基础上补充方法签名、可见性、参数类型等实现细节。例如:
// 概念模型:Book可以借阅
// 设计模型:明确方法签名
public abstract boolean canBorrow();
public abstract boolean canOrder();
public abstract int getBorrowLimit();
// 概念模型:BookCopy有位置
// 设计模型:增加移动追踪
public void move(String date, Location to);
public void reserve(String date, LocalDate until, String userId);
两阶类图的优势体现在:
- 降低认知负荷:先理解领域概念,再关注实现细节,符合人类认知规律
- 支持迭代设计:概念模型稳定后,设计模型可以独立演进而不影响整体架构
- 提升代码质量:设计模型中的方法签名直接对应代码实现,减少"边想边写"带来的设计缺陷
2.3 架构设计与UML模型的追踪关系
2.3.1 类图追踪:从模型到代码的精准映射
在最终的代码实现中,UML类图与实际代码保持了严格的一致性:
| UML元素 | 代码实现 | 追踪验证 |
|---|---|---|
Book抽象类 | public abstract class Book | ✅ 完全一致 |
BookCopy类属性 | private String id; private Location location; | ✅ 完全一致 |
User#creditScore | private int creditScore; | ✅ 完全一致 |
Library#books | private Map<String, Book> books; | ✅ 完全一致 |
canBorrow()抽象方法 | public abstract boolean canBorrow(); | ✅ 完全一致 |
特别值得注意的是,在第三阶段迭代中,UML模型增加了overduePenalized属性和对应的getter/setter方法,代码实现也同步更新:
// uml_ultimate.mdj 中的设计模型
{"_type":"UMLAttribute","_id":"M004AA","_parent":{"$ref":"M004"},"name":"overduePenalized","visibility":"private","type":"boolean"}
// 代码实现
private boolean overduePenalized;
public boolean isOverduePenalized() { return overduePenalized; }
public void setOverduePenalized(boolean penalized) { this.overduePenalized = penalized; }
2.3.2 顺序图追踪:消息交互的精确还原
顺序图(Sequence Diagram)描述了对象之间的消息传递时序。在第二次作业中,我们设计了"预约-取书"的交互流程:
用户 -> 图书馆: orderNewBook(dateStr, uid, isbn, req)
图书馆 -> 用户: hasAppointment()
图书馆 -> 用户: setAppointIsbn(isbn)
用户 -> 图书馆: getOrderedBook(today, dateStr, uid, isbn, req)
图书馆 -> 副本: getReserveUser()
图书馆 -> 副本: move(dateStr, Location.USER)
图书馆 -> 用户: borrow(copy)
图书馆 -> 用户: clearAppointment()
代码实现中,这些消息对应具体的方法调用:
// Library.java
@SendMessage(from = "Library", to = "User")
public void orderNewBook(String dateStr, String uid, String isbn, LibraryReqCmd req) {
doOrder(dateStr, uid, isbn, req);
}
@Trigger(from = "AppointmentOffice", to = {"User"})
@SendMessage(from = "Library", to = "User")
private void doPick(LocalDate today, String dateStr, String uid, String isbn, LibraryReqCmd req) {
// ... 验证逻辑
reservedCopy.move(dateStr, Location.USER);
reservedCopy.clearReserve();
user.borrow(reservedCopy);
user.clearAppointment();
}
关键洞察:顺序图中的每个消息(Message)必须对应类中真实存在的方法,且消息的source和target类之间需存在关联关系。这是评测器校验的核心规则。
2.3.3 状态图追踪:状态迁移的逻辑验证
状态图(Statechart Diagram)是第三阶段的核心交付物,描述了BookCopy在其生命周期中的状态变化:
状态集合:
- Bookshelf (BS)
- TreasuredBookshelf (TBS)
- ReadingRoom (RR)
- BorrowReturnOffice (BRO)
- AppointmentOffice (AO)
- User
迁移规则示例:
Bookshelf -> User: doBorrow() [guard: creditScore > 80]
User -> BorrowReturnOffice: doReturn()
Bookshelf -> ReadingRoom: doRead() [guard: creditScore >= 60]
ReadingRoom -> BorrowReturnOffice: doRestore()
代码实现中,状态迁移通过Location枚举和move()方法实现:
// BookCopy.java
public void move(String date, Location to) {
Location from = this.location;
this.location = to;
traces.add(new Trace(date, from, to)); // 记录状态迁移历史
}
状态图必须满足的约束(根据评测规则):
- 有且仅有一个无名起始状态(仅外出迁移)
- 0或1个终止状态(仅进入迁移)
- 任意状态S必须存在从起始状态到S的迁移路径
- 若存在终止状态,则从S到终止状态也必须存在路径
代码实现通过Location枚举的六个值和move()方法的调用确保所有状态可达。
三、大模型辅助正向建模的实践与思考
3.1 大模型在架构设计中的定位
在第四单元的实践中,我尝试使用大语言模型(LLM)辅助完成UML建模和架构设计任务。通过多次迭代,我总结出大模型在复杂场景中的三种角色:
3.1.1 概念提取器:从需求到实体识别
提示词示例:
请分析以下图书馆管理系统需求,提取核心领域实体及其属性:
- 图书分为A/B/C三种类型,各有不同的借阅规则
- 图书副本可以在书架、阅览室、借还处、预约处、用户手中流转
- 用户有学分系统,学分影响借阅权限
大模型输出:
- Book(类型、ISBN、副本列表)
- BookCopy(位置、预约状态、借阅截止日期)
- User(学分、借阅记录、预约状态)
- Library(图书集合、用户集合)
人工验证:实体识别准确,但需要补充Rating(评分)和Trace(迁移记录)两个辅助实体。
3.1.2 关系推断器:从实体到关联设计
提示词示例:
已知以下实体,请推断它们之间的关联关系及多重性:
- Library
- Book (isbn, type)
- BookCopy (id, location)
- User (id, creditScore)
大模型输出:
- Library 1 → * Book(图书馆管理多本图书)
- Book 1 → * BookCopy(一本图书有多个副本)
- User * → * BookCopy(用户可借阅多本副本,副本可被多用户借阅过)
人工验证:关联关系正确,但需注意User与BookCopy的实际关联是"当前借阅"(1→*),而非历史借阅。
3.1.3 代码生成器:从模型到实现 skeleton
提示词示例:
根据以下UML类图设计,生成Java代码骨架:
class BookCopy {
-id: String
-location: Location
-traces: List<Trace>
+move(date, to)
+reserve(date, until, userId)
}
大模型输出:
public class BookCopy {
private String id;
private Location location;
private List<Trace> traces;
public BookCopy(String id) {
this.id = id;
this.location = Location.BS;
this.traces = new ArrayList<>();
}
public void move(String date, Location to) {
// TODO: 实现位置移动逻辑
}
public void reserve(String date, LocalDate until, String userId) {
// TODO: 实现预约逻辑
}
}
人工验证:代码骨架结构正确,但需补充业务逻辑细节(如traces记录、状态校验等)。
3.2 引导大模型的关键技巧
通过实践,我总结出以下引导大模型完成架构设计任务的经验:
3.2.1 分层提示:从抽象到具体
不要一次性抛出完整需求,而是按照"概念模型→设计模型→代码实现"的顺序逐步引导:
第一轮:请识别领域实体
第二轮:请定义实体间的关联关系
第三轮:请为每个类设计方法签名
第四轮:请生成代码骨架
第五轮:请补充业务逻辑
3.2.2 约束明确:提供评测规则
在提示词中明确说明评测器的校验规则,避免大模型生成不符合规范的设计:
注意:
- UML顺序图中,lifeline必须对应具体实现的类(不能是抽象类)
- message必须与类中真实存在的方法名完全一致
- 状态图必须有且仅有一个无名起始状态
- 任意状态必须从起始状态可达
3.2.3 交叉验证:人工审核关键点
大模型擅长模式匹配,但不擅长业务逻辑推理。以下关键点必须人工验证:
- 业务规则正确性:如学分阈值(>80可借阅、>40可读A类书)
- 边界条件处理:如预约过期、逾期罚款的时机
- 状态图可达性:确保所有状态都能从起始状态到达
- 方法签名一致性:UML模型与代码实现的方法名、参数类型必须完全匹配
3.3 大模型的局限性
在实践中,我也意识到大模型的局限性:
- 缺乏业务上下文理解:大模型可以生成结构正确的UML,但无法理解"为什么预约保留5天"这样的业务决策
- 容易产生幻觉:在复杂交互场景中,大模型可能生成不存在的方法调用
- 难以处理迭代变更:当需求变更时,大模型容易遗漏旧设计的清理工作
结论:大模型是强大的辅助工具,但不能替代开发者的架构思维。最佳实践是"大模型生成骨架,人工填充灵魂"。
四、四个单元架构设计思维的演进
4.1 第一单元:面向过程的结构化思维
任务特征:表达式解析、多项式求导
设计模式:过程式编程 + 简单的类封装
思维特点:
- 关注"如何实现功能"
- 代码组织以函数为中心
- 数据结构与算法分离
典型代码结构:
public class Main {
public static void main(String[] args) {
String input = sc.nextLine();
Node expr = parse(input);
Node derivative = derive(expr);
System.out.println(derivative.toString());
}
}
局限性:随着需求复杂度增加(如加入三角函数、指数函数),代码逐渐演变为"面条代码",难以维护。
4.2 第二单元:面向对象的继承与多态
任务特征:电梯调度系统
设计模式:策略模式 + 生产者-消费者模型
思维特点:
- 关注"如何组织类"
- 使用继承实现代码复用
- 使用多态实现行为扩展
典型设计:
abstract class Elevator {
abstract void processRequest(Request req);
}
class ElevatorA extends Elevator {
void processRequest(Request req) {
// A型电梯的处理逻辑
}
}
class ElevatorB extends Elevator {
void processRequest(Request req) {
// B型电梯的处理逻辑
}
}
进步:通过多态消除了大量的if-else分支,新增电梯类型只需添加新类而不修改旧代码(开闭原则)。
4.3 第三单元:设计模式的系统化应用
任务特征:社交网络分析与JML规格验证
设计模式:工厂模式、观察者模式、装饰器模式
思维特点:
- 关注"如何解耦组件"
- 使用设计模式解决常见问题
- 重视接口的抽象设计
典型应用:
// 工厂模式:创建不同类型的Network
interface NetworkFactory {
Network createNetwork();
}
// 观察者模式:消息通知机制
interface Observer {
void update(Message msg);
}
class User implements Observer {
void update(Message msg) {
// 处理新消息
}
}
突破:开始从"实现功能"转向"设计架构",意识到好的设计可以让代码自然生长。
4.4 第四单元:模型驱动的正向设计
任务特征:图书馆管理系统 + UML正向建模
设计模式:状态模式 + 命令模式 + 模型驱动架构
思维特点:
- 关注"系统应该是什么"
- 先建模后编码,模型即设计
- 强调模型与代码的追踪关系
典型工作流:
需求分析 → 概念模型 → 设计模型 → 代码骨架 → 业务逻辑 → 测试验证
↓ ↓ ↓ ↓ ↓ ↓
领域实体 UML类图1 UML类图2 Java类 方法实现 单元测试
质变:从"边写边想"到"先想后写",从"代码即设计"到"模型即设计"。UML不再是事后补画的文档,而是指导开发的蓝图。
4.5 演进总结
| 单元 | 核心思维 | 设计重心 | 质量指标 |
|---|---|---|---|
| 第一单元 | 面向过程 | 算法正确性 | 功能是否实现 |
| 第二单元 | 面向对象 | 类的设计 | 代码是否复用 |
| 第三单元 | 设计模式 | 组件解耦 | 架构是否灵活 |
| 第四单元 | 模型驱动 | 系统设计 | 模型是否准确 |
思维跃迁轨迹:
如何实现(How) → 如何组织(Structure) → 如何解耦(Decouple) → 应该是什么(What)
五、四个单元测试思维的演进
5.1 第一单元:暴力测试——"能跑就行"
测试策略:手动输入几个样例,看输出是否正确
测试用例:
- 正常情况:
x + 2*x→3 - 边界情况:
0→0 - 特殊情况:
x*x*x→3*x*x
问题:
- 测试覆盖率极低
- 无法处理大规模随机测试
- 回归测试完全靠人工
5.2 第二单元:自动化测试——"脚本辅助"
测试策略:编写Python脚本生成测试用例,对比输出
测试工具:
import subprocess
import random
def generate_test():
# 随机生成电梯请求序列
requests = []
for _ in range(random.randint(10, 50)):
requests.append(f"[{random.randint(1,100)}]FROM-{random.randint(1,10)}-TO-{random.randint(1,10)}")
return "\n".join(requests)
# 运行程序并对比输出
result = subprocess.run(["java", "-jar", "elevator.jar"], input=generate_test(), capture_output=True, text=True)
进步:
- 测试覆盖率提升
- 支持大规模随机测试
- 可重复执行
局限:
- 脚本逻辑复杂度高
- 难以验证中间状态(如电梯内部请求队列)
5.3 第三单元:规格测试——"契约验证"
测试策略:基于JML规格编写测试用例,验证方法是否符合契约
测试方法:
// JML规格
/*@ requires \contains(user1) && \contains(user2);
@ ensures \result == (\sum int i; isLinked(user1, i) && isLinked(user2, i); 1);
@*/
public int queryCircleCount(int id1, int id2);
// 测试用例
@Test
public void testQueryCircleCount() {
network.addPerson(person1);
network.addPerson(person2);
network.addRelation(person1, person3);
network.addRelation(person2, person3);
assertEquals(1, network.queryCircleCount(person1.getId(), person2.getId()));
}
突破:
- 测试从"黑盒"转向"白盒"
- 基于规格验证,而非基于样例猜测
- 支持单元测试框架(JUnit)
5.4 第四单元:模型测试——"一致性校验"
测试策略:验证UML模型与代码实现的一致性,确保模型驱动的正确性
测试维度:
5.4.1 结构一致性测试
- 类图中的类、属性、方法是否在代码中存在
- 关联关系的多重性是否正确实现
- 继承关系是否准确映射
5.4.2 行为一致性测试
- 顺序图中的消息是否对应真实方法调用
- 状态图中的迁移是否在代码中实现
- 守卫条件(guard)是否转化为if判断
5.4.3 约束一致性测试
- UML模型的可见性(public/private/protected)是否与代码一致
- 抽象方法是否在子类中实现
- 枚举值是否完整覆盖
测试工具链:
UML模型 (.mdj)
↓
模型解析器
↓
一致性校验器 ← → 代码解析器 (JavaParser)
↓
测试报告
质变:测试从"验证代码是否正确"升级为"验证模型与代码是否一致",测试的重心从实现细节转向设计意图。
5.5 测试思维总结
| 单元 | 测试理念 | 测试方法 | 验证目标 |
|---|---|---|---|
| 第一单元 | 功能验证 | 手动测试 | 输出是否正确 |
| 第二单元 | 自动化验证 | 脚本对比 | 大规模用例覆盖 |
| 第三单元 | 契约验证 | JML规格测试 | 方法是否符合契约 |
| 第四单元 | 模型验证 | 一致性校验 | 模型与代码是否一致 |
测试思维跃迁:
黑盒测试 → 白盒测试 → 规格测试 → 模型测试
↓ ↓ ↓ ↓
样例验证 代码覆盖 契约满足 设计一致
六、课程收获与反思
6.1 技术能力的提升
6.1.1 Java编程能力
- 熟练掌握面向对象编程的核心概念(封装、继承、多态)
- 能够运用设计模式解决常见架构问题
- 理解泛型、反射、注解等高级特性
6.1.2 UML建模能力
- 能够使用类图、顺序图、状态图表达系统设计
- 理解正向建模的工作流与价值
- 掌握模型与代码的追踪方法
6.1.3 测试工程能力
- 从手工测试到自动化测试的工程化思维
- 基于规格的测试用例设计方法
- 模型驱动的一致性校验技术
6.2 架构思维的蜕变
6.2.1 从"实现者"到"设计者"
课程初期,我关注的是"如何让代码跑起来";课程后期,我思考的是"如何让设计更优雅"。这种转变不仅体现在代码质量上,更体现在解决问题的思维方式上。
6.2.2 从"局部优化"到"全局视角"
第一单元时,我会为了一个算法的几毫秒优化绞尽脑汁;第四单元时,我会先思考整体架构是否合理,再关注局部实现。架构思维让我学会了"先做对,再做好"。
6.2.3 从"经验驱动"到"模型驱动"
过去的开发依赖个人经验,容易陷入"试错-修改"的循环;现在的开发基于UML模型,能够在编码之前发现设计缺陷,大幅降低返工成本。
6.3 工程实践的感悟
6.3.1 好的设计是演进而来的,不是规划出来的
四个单元的作业难度递增,架构也在不断重构。我深刻认识到,没有一蹴而就的完美架构,只有在迭代中不断演进的合理架构。关键是保持架构的灵活性,让系统能够"自然生长"。
6.3.2 测试不是负担,而是安全网
从第一单元的"懒得写测试"到第四单元的"先写测试再写代码",我意识到测试不是开发的负担,而是保障代码质量的必要手段。好的测试让你敢于重构,敢于变更。
6.3.3 工具是手段,思维是核心
大模型、UML工具、测试框架都是辅助手段,真正决定代码质量的是开发者的架构思维。工具可以提升效率,但不能替代思考。
6.4 不足与展望
6.4.1 仍需提升的方面
- 性能优化意识:在复杂场景下,对时间复杂度和空间复杂度的敏感度仍需加强
- 并发编程能力:课程作业以单线程为主,对多线程、锁机制的实践经验不足
- 团队协作经验:个人作业无法模拟真实团队协作中的代码合并、冲突解决等场景
6.4.2 未来学习方向
- 深入设计模式:系统学习GoF 23种设计模式的适用场景与实现细节
- 掌握领域驱动设计(DDD):将第四单元的正向建模思想延伸到更复杂的业务场景
- 学习微服务架构:从单体应用走向分布式系统,理解服务拆分、API设计、数据一致性等挑战
七、结语
2026OO课程是我大学期间最具挑战也最具收获的课程之一。从第一单元的手忙脚乱到第四单元的从容应对,从面向过程到模型驱动,每一次重构都是思维的蜕变,每一次调试都是能力的提升。
特别感谢老师的悉心指导和助教的耐心答疑。课程的结束不是终点,而是新的起点。面向对象的思想、架构设计的思维、测试驱动的理念,将伴随我未来的整个职业生涯。
最后,用一句话总结这门课程带给我的改变:
"代码是写给人看的,只是恰好能在机器上运行。" —— 好的架构让代码自解释,好的设计让系统自演进。
相关推荐
北航OO课作业代码
北航OO课的作业代码,从电梯到出租车,你想要的都在里面。注释充足,供有需要的同学参考(切勿抄袭哦~)
ALV OO 模板
ALV OO 模板
uml建模讲义 OO方法、RUP与UML建模
uml建模ppt uml建模讲义 OO方法、RUP与UML建模
ephec_java_oo:2014-2015 年 Java OO 课程期间进行的练习
Java 面向对象编程 在“Java Object Oriented Programming”课程期间进行的练习 - EPHEC 2014-2015
ABAP OO的八个理由
1.ABAP OO具有更高层次的数据封装性 2.ABAP OO可以实现一个类的多个实例 3.ABAP OBJECT通过继承进一步增强了程序代码的可重用性 4.ABAP OO是你可以通过接口(interface)来调用对象的业务逻辑,而不是直接去使用对象,这样就避免了你需要详细了解每一个对象的特定功能 5.ABAP OO非常容易与事件驱动的模式结合在一块 6.ABAP OO更加明确所以更易于使用 7.ABAP OO具有更加清晰的语法和语义规则 8.ABAP的一些新技术只能通过ABAP OO来实现


