309
社区成员
发帖
与我相关
我的任务
分享2026年面向对象设计与构造课程已圆满落幕。回顾四个单元的学习历程,从基础的Java语法训练到复杂的系统设计,从手工编码到UML正向建模,每一阶段都是一次思维的跃迁。第四单元作为课程的收官之作,引入了正向建模与开发的全新范式,让我深刻认识到UML模型在现代软件工程中的核心价值。本文将从第四单元的实践出发,系统总结正向建模的实践经验、架构设计思维演进、测试思维蜕变以及整个课程的收获与感悟。
正向建模(Forward Modeling)是指从抽象的UML模型出发,通过模型驱动的方式生成或指导代码实现的过程。与传统的"先写代码后画图"不同,正向建模强调"模型先行",要求开发者在编码之前完成系统的结构化设计,确保模型与代码之间建立严格的追踪关系。
在第四单元的三次作业中,我们分别完成了:
第四单元最核心的设计理念是"两阶类图"(Two-Tier Class Diagram),即先构建概念模型(Conceptual Model),再细化为设计模型(Design Model)。这种分层建模策略在正向建模过程中发挥了至关重要的作用。
概念模型关注"系统是什么",主要提取领域中的核心实体及其关系。在图书馆管理系统中,概念模型包含:
核心实体:
- Book(图书):抽象类,定义ISBN、类型、副本列表等属性
- BookCopy(图书副本):具体实例,追踪位置、借阅状态、历史记录
- User(用户):管理学分、借阅记录、预约状态
- Library(图书馆):协调所有实体,处理业务请求
- Location(位置枚举):BS/TBS/BRO/AO/RR/USER六种位置
概念模型的价值在于:
设计模型关注"系统怎么做",在概念模型的基础上补充方法签名、可见性、参数类型等实现细节。例如:
// 概念模型: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);
两阶类图的优势体现在:
在最终的代码实现中,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; }
顺序图(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类之间需存在关联关系。这是评测器校验的核心规则。
状态图(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)); // 记录状态迁移历史
}
状态图必须满足的约束(根据评测规则):
代码实现通过Location枚举的六个值和move()方法的调用确保所有状态可达。
在第四单元的实践中,我尝试使用大语言模型(LLM)辅助完成UML建模和架构设计任务。通过多次迭代,我总结出大模型在复杂场景中的三种角色:
提示词示例:
请分析以下图书馆管理系统需求,提取核心领域实体及其属性:
- 图书分为A/B/C三种类型,各有不同的借阅规则
- 图书副本可以在书架、阅览室、借还处、预约处、用户手中流转
- 用户有学分系统,学分影响借阅权限
大模型输出:
人工验证:实体识别准确,但需要补充Rating(评分)和Trace(迁移记录)两个辅助实体。
提示词示例:
已知以下实体,请推断它们之间的关联关系及多重性:
- Library
- Book (isbn, type)
- BookCopy (id, location)
- User (id, creditScore)
大模型输出:
人工验证:关联关系正确,但需注意User与BookCopy的实际关联是"当前借阅"(1→*),而非历史借阅。
提示词示例:
根据以下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记录、状态校验等)。
通过实践,我总结出以下引导大模型完成架构设计任务的经验:
不要一次性抛出完整需求,而是按照"概念模型→设计模型→代码实现"的顺序逐步引导:
第一轮:请识别领域实体
第二轮:请定义实体间的关联关系
第三轮:请为每个类设计方法签名
第四轮:请生成代码骨架
第五轮:请补充业务逻辑
在提示词中明确说明评测器的校验规则,避免大模型生成不符合规范的设计:
注意:
- UML顺序图中,lifeline必须对应具体实现的类(不能是抽象类)
- message必须与类中真实存在的方法名完全一致
- 状态图必须有且仅有一个无名起始状态
- 任意状态必须从起始状态可达
大模型擅长模式匹配,但不擅长业务逻辑推理。以下关键点必须人工验证:
在实践中,我也意识到大模型的局限性:
结论:大模型是强大的辅助工具,但不能替代开发者的架构思维。最佳实践是"大模型生成骨架,人工填充灵魂"。
任务特征:表达式解析、多项式求导
设计模式:过程式编程 + 简单的类封装
思维特点:
典型代码结构:
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());
}
}
局限性:随着需求复杂度增加(如加入三角函数、指数函数),代码逐渐演变为"面条代码",难以维护。
任务特征:电梯调度系统
设计模式:策略模式 + 生产者-消费者模型
思维特点:
典型设计:
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分支,新增电梯类型只需添加新类而不修改旧代码(开闭原则)。
任务特征:社交网络分析与JML规格验证
设计模式:工厂模式、观察者模式、装饰器模式
思维特点:
典型应用:
// 工厂模式:创建不同类型的Network
interface NetworkFactory {
Network createNetwork();
}
// 观察者模式:消息通知机制
interface Observer {
void update(Message msg);
}
class User implements Observer {
void update(Message msg) {
// 处理新消息
}
}
突破:开始从"实现功能"转向"设计架构",意识到好的设计可以让代码自然生长。
任务特征:图书馆管理系统 + UML正向建模
设计模式:状态模式 + 命令模式 + 模型驱动架构
思维特点:
典型工作流:
需求分析 → 概念模型 → 设计模型 → 代码骨架 → 业务逻辑 → 测试验证
↓ ↓ ↓ ↓ ↓ ↓
领域实体 UML类图1 UML类图2 Java类 方法实现 单元测试
质变:从"边写边想"到"先想后写",从"代码即设计"到"模型即设计"。UML不再是事后补画的文档,而是指导开发的蓝图。
| 单元 | 核心思维 | 设计重心 | 质量指标 |
|---|---|---|---|
| 第一单元 | 面向过程 | 算法正确性 | 功能是否实现 |
| 第二单元 | 面向对象 | 类的设计 | 代码是否复用 |
| 第三单元 | 设计模式 | 组件解耦 | 架构是否灵活 |
| 第四单元 | 模型驱动 | 系统设计 | 模型是否准确 |
思维跃迁轨迹:
如何实现(How) → 如何组织(Structure) → 如何解耦(Decouple) → 应该是什么(What)
测试策略:手动输入几个样例,看输出是否正确
测试用例:
x + 2*x → 30 → 0x*x*x → 3*x*x问题:
测试策略:编写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)
进步:
局限:
测试策略:基于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()));
}
突破:
测试策略:验证UML模型与代码实现的一致性,确保模型驱动的正确性
测试维度:
测试工具链:
UML模型 (.mdj)
↓
模型解析器
↓
一致性校验器 ← → 代码解析器 (JavaParser)
↓
测试报告
质变:测试从"验证代码是否正确"升级为"验证模型与代码是否一致",测试的重心从实现细节转向设计意图。
| 单元 | 测试理念 | 测试方法 | 验证目标 |
|---|---|---|---|
| 第一单元 | 功能验证 | 手动测试 | 输出是否正确 |
| 第二单元 | 自动化验证 | 脚本对比 | 大规模用例覆盖 |
| 第三单元 | 契约验证 | JML规格测试 | 方法是否符合契约 |
| 第四单元 | 模型验证 | 一致性校验 | 模型与代码是否一致 |
测试思维跃迁:
黑盒测试 → 白盒测试 → 规格测试 → 模型测试
↓ ↓ ↓ ↓
样例验证 代码覆盖 契约满足 设计一致
课程初期,我关注的是"如何让代码跑起来";课程后期,我思考的是"如何让设计更优雅"。这种转变不仅体现在代码质量上,更体现在解决问题的思维方式上。
第一单元时,我会为了一个算法的几毫秒优化绞尽脑汁;第四单元时,我会先思考整体架构是否合理,再关注局部实现。架构思维让我学会了"先做对,再做好"。
过去的开发依赖个人经验,容易陷入"试错-修改"的循环;现在的开发基于UML模型,能够在编码之前发现设计缺陷,大幅降低返工成本。
四个单元的作业难度递增,架构也在不断重构。我深刻认识到,没有一蹴而就的完美架构,只有在迭代中不断演进的合理架构。关键是保持架构的灵活性,让系统能够"自然生长"。
从第一单元的"懒得写测试"到第四单元的"先写测试再写代码",我意识到测试不是开发的负担,而是保障代码质量的必要手段。好的测试让你敢于重构,敢于变更。
大模型、UML工具、测试框架都是辅助手段,真正决定代码质量的是开发者的架构思维。工具可以提升效率,但不能替代思考。
2026OO课程是我大学期间最具挑战也最具收获的课程之一。从第一单元的手忙脚乱到第四单元的从容应对,从面向过程到模型驱动,每一次重构都是思维的蜕变,每一次调试都是能力的提升。
特别感谢老师的悉心指导和助教的耐心答疑。课程的结束不是终点,而是新的起点。面向对象的思想、架构设计的思维、测试驱动的理念,将伴随我未来的整个职业生涯。
最后,用一句话总结这门课程带给我的改变:
"代码是写给人看的,只是恰好能在机器上运行。" —— 好的架构让代码自解释,好的设计让系统自演进。