面向对象课程总结报告

侯林赫-24373503 2026-06-26 16:24:22

OO课程总结报告

一、总结第四单元所实践的正向建模与开发,重点分析两阶类图在正向建模过程中的作用

1.1 关于正向建模

第四单元的作业围绕一个图书馆管理系统展开,涉及书籍的借还、预约、取书、查询、在馆阅读、续借、信用分等业务功能。与前三单元最大的不同在于,第四单元要求我们先画类图,再写代码——这就是"正向建模"(Forward Modeling)的核心:在设计阶段用 UML 模型来表达架构决策,再以模型为蓝图指导实现。

一阶类图和二阶类图在整个开发流程中扮演着不同的角色。

一阶类图的目标是在宏观层面回答"这个系统有哪些核心类?它们之间的协作关系是什么?"。在图书馆管理系统中,一阶类图需要(至少)画出 LibraryManagerBookshelfBorrowReturnOfficeAppointmentOfficeUserBook 等核心类,以及它们的关联关系。这一阶段的核心产出是"设计者是否对系统的分解方式有清晰的认知"。

二阶类图在一阶类图的基础上做精化——补全所有类的方法、属性、可见性、类型,以及类之间的关系(继承、实现、关联)。评测系统会逐项检查二阶类图与代码的一致性(R2-R5 检查),确保类图真实反映了实现。

从作业体验出发,我总结两阶类图在正向建模中有以下作用:

第一,强制在写代码前做架构决策。 在没有正向建模要求时,我通常会边写边改——想到一个新类就创建,发现某个类职责过重就拆分。这种方式虽然灵活,但容易导致架构缺乏整体性。一阶类图的截稿时间(早于代码提交)迫使我必须先想清楚"这个系统有哪些组成部分"再动手写实现。即使类图画得不够精确,这个思考过程本身就是有价值的。

但是其实我之前写代码也是想半天不动手,老想着想到一个好的结构再去开始写,但是发现不开始写的话把握不住结构,不太知道哪里好办可以封在一起,哪里不好办要拆开。所以其实还是边写边改的比较多

第二,建立设计与实现之间的对照关系。 二阶类图与代码的相似度检查,本质上是要求设计文档和实现之间可以相互映射。在三次作业中,我越来越习惯在修改代码后同步更新类图——这是一种设计审查:如果我发现在类图上很难表达某个修改,或者修改后类图变得很难看,那往往说明这个修改的架构合理性有待商榷。同步更新类图是通过可视化方式帮助我审视修改的架构合理性。

但是由于starUml确实太难用了,正儿八经两阶段类图全是手画太费劲了,从数据上也看得出来很多同学两阶段类图相似度100%

二、总结第四单元作业的架构设计,并对比分析最终的代码设计和UML模型设计之间的追踪关系

2.1 架构总体设计

第四单元的图书馆管理系统采用Facade + Handler 模式架构:

  • LibraryManager 作为系统门面(Facade),持有所有子组件的引用(BookshelfBorrowReturnOfficeAppointmentOfficeReadingRoom 等),是所有外部命令的入口。
  • Handler 模式:每种命令类型对应一个独立的 Handler 类(BorrowHandlerReturnHandlerOrderHandler 等),都实现 RequestHandler 接口。Handler 在 LibraryManager 的构造函数中注册到 EnumMap<LibraryReqCmd.Type, RequestHandler> 中。
  • ArrangeHandler 特殊处理:ArrangeHandler 由"开馆/闭馆"事件触发,是一个独立的内部流程(而非 RequestHandler 的实现),单独暴露 arrangeOpen()arrangeClose() 方法。
  • 位置类BookshelfBorrowReturnOfficeAppointmentOfficeReadingRoom):各自管理对应位置的书,职责单一——只负责增删查,不包含业务流程。
  • 实体类BookUserReservationMovingRecord):携带核心状态和领域逻辑。

这个架构的设计原则是把变化隔离到独立的 handler 中。每一次作业迭代新增的功能几乎都表现为"新增 handler 类"或"修改某个 handler 的内部逻辑",而整体的处理流程(processCommand)不需要变化。

2.2 从 hw13 到 hw15 的架构演进

版本新增架构元素架构变更类型
hw13Bookshelf、BorrowReturnOffice、AppointmentOffice + 5 个 Handler初始架构
hw14ReadingRoom、TreasuredBookshelf + ReadHandler、RestoreHandler、GradeHandler增加新位置,扩展 ArrangeHandler
hw15RenewHandler、信用分系统、QcsCmd在现有框架内新增 handler 和字段,无结构变动

hw13 建立了基础架构框架。hw14 引入了 ReadingRoom 和"精品书架"的概念——要求按评分将书自动分到不同的书架。这个改动虽然新增了三个 handler,但核心架构模式不变。hw15 的信用分系统是最大的功能变更,大量修改了 User 的内部逻辑(信用分阈值检查、逾期扣分、续借延期等),但从架构角度看,它只是新增了一个 RenewHandler 并修改了现有 handler 中的权限校验逻辑——框架层的 processCommand 依然保持稳定。

三、根据使用大模型辅助正向建模的体验,总结分析如何引导大模型在复杂场景中完成架构设计任务

考虑一下从需求到代码的流程

先是各种调研,产生需求,这一步其实很重要,ai其实不太好弄这一步,也许以后可以考虑承担一些这些工作
然后根据需求初步思考代码的结构,这是这一单元,以及OO课程主要想要交给我们的
然后是根据结构落实代码,这个环节基本可以全部丢给大模型来完成,如果上一步做得好的话

但是一个困难的问题就是你不进入第三步,没有特别仔细地思考完成的细节的经验,那代码结构其实思考不好
但是时代也变了,干啥都是vim+手敲的话肯定慢,也许十几年开发经验的会快很多,但是客观来说在大模型的冲击下已经很难演变出这种手敲做的比大模型好的老艺术家了
所以我们要考虑的就是多大程度上进入第三步

我的一种做法是

先尽力用我的话描述需求
然后讨论怎么把需求变成结构,结构出来以后我要审一遍并且改一遍
然后让ai根据结构写代码,写的时候告诉他一次改一个函数,我一个函数一个函数的看,不满意我就否决掉,虽然不写,但是看了一遍,其实对系统也是有把握的

这样子代码质量应该有基本的保证,但是还是有下面两个问题

  1. 描述不够清楚的结构,他自我发挥的时候就不太好把控
  2. 否决行为会污染上下文,让代码质量有所降低

但是确实快

但是说实话,我vibe第三四单元的时候,确实感觉没有写第一单元的时候的那种快乐

ai这个东西对于coding领域感觉就像是工业化对小手工业者的冲击,有些东西工业化以后确实没有灵魂,质量上也稍微差点意思
但是缺的那点灵魂和一点点质量相对于速度而言啥也不是
所以传统coding意义更大的地方,要不然是反复重用的系统性代码,要不是对安全性要求极高的代码,那种小玩具小应用完全不值钱了

其实也许比这个比喻还可怕
可能更像是围棋领域alphaGo对人类棋手的冲击,就是以人类先前的思考和大脑的算力,没有把这个体系琢磨透,但是ai琢磨的更透
在围棋领域现在搞得人类棋手学ai棋谱,在代码领域ai能找到好多系统性的安全问题(如果claude的mythos不是纯吹牛逼的话)

四、总结自己在四个单元中架构设计思维的演进

4.1 第一单元:封装,多态和递归结构

第一单元的任务是解析和化简多项式表达式,从最简单的单变量多项式(hw1)到支持自定义函数(hw2)、再到递归函数和求导(hw3)。

  • hw1 用 ArrayList<Unit> 表达多项式,架构是扁平的:Parser 解析出 Expr → Term → Factor 树,然后 toPoly() 化简。
  • hw2 引入 exp(...) 函数后,Unit 必须嵌套一个 Poly 才能表达 exp(x^2+1),于是内部表示从 ArrayList<Unit> 变成了 HashMap<UnitType, BigInteger>UnitType 本身可以包含一个 Poly。这个变化直接推动了解析器和化简器的重构——是"旧的表达方式不够用了"驱动了重构,而非单纯"想改架构"。
  • hw3 引入导数后,Poly.derive()UnitType.derive() 的加入让系统从"纯化简"升级为"化简 + 变换"。

这个阶段我的最大收获是:架构的变化是由数据结构的演化驱动的,而非凭空发生的。 选择什么样的数据结构表达核心概念,决定了后续架构变动的方向和成本。

4.2 第二单元:从"单线程思维"到"并发安全设计"

第二单元的任务是模拟多电梯调度系统,从单轿厢(HW5)到集中调度(HW6)、再到双轿厢同井道(HW7)。

第二单元最大的冲击来自于多线程。第一单元的所有代码都在单线程中运行,方法调用的顺序就是执行顺序,状态变化是可预测的。但第二单元引入了电梯线程、调度线程和输入线程的并发执行,我不得不重新思考"状态"的含义:

  • 一个 Channel 中的请求列表,在单线程中就是一个 ArrayList,在多线程中就必须是 synchronized + wait/notify 保护的临界区。
  • 一个电梯的决策(DecisionContext.giveActionAdvise()),在单线程中可以基于"当前快照"计算,在多线程中则必须通过 snapShot() 做原子复制,否则决策时看到的状态和实际行动时的状态可能不一致。
  • 全局调度(ElevatorsCentralCtrl)从 HW5 的不需要,到 HW6 必须引入,因为调度需要知道所有电梯的实时负载——而"实时负载"在多线程环境中意味着读取操作需要看到最新的写入,因此必须使用 ConcurrentHashMap 或者 synchronized 保护的状态表。

HW7 的双轿厢同井道进一步挑战了架构抽象能力:两个独立的电梯线程共享同一个井道,不能同时停在同一楼层。这个约束不能在电梯线程内部解决,必须在井道层面做协调(FloorLock)。于是我引入了 ElevatorShaft 作为一个新的架构层级,封装了双轿厢的协调逻辑。

这个阶段我的核心认知转变是:多线程环境下的架构设计,本质上是在回答"哪些状态是共享的、哪些状态是私有的、共享状态如何安全访问"这三个问题。 好的架构是让共享状态的范围最小化、访问路径清晰化,而非消灭锁本身。

4.3 第三单元:从"自由设计"到"规约驱动"

第三单元的任务是构建一个社交网络/视频平台系统,使用 JML(Java Modeling Language)作为规格语言,要求实现完全符合规格定义的接口。

第三单元与第一、第二单元最大的不同在于:架构是由给定的接口和规格约束推导出来的,而非自由设计的。我需要实现的 NetworkUserVideo 类必须满足预先写好的 JML 规格,这些规格精确描述了每个方法的输入输出、异常条件和状态变更。

这个阶段我的架构思维经历了两次重要转变:

第一次:从"怎么实现"到"该实现什么"。 在 JML 规格下,我写代码之前必须先读懂规格中的 requires(前置条件)、ensures(后置条件)和 signals(异常条件)。规格告诉我"这个方法必须在什么条件下做什么事",而非"这个方法如何做"。这意味着设计决策的重点从"如何选择实现算法"前移到了"如何从规格中推导出正确的数据结构和状态变量"。

第二次:从"状态自由"到"状态可验证"。 JML 规格的核心约束之一是 assignable 子句,它明确规定了哪些变量可以被修改、哪些必须保持不变。这迫使我仔细区分"基本状态"(用户表、视频表、关注关系)和"派生状态"(互关数量、热力值、贡献排行)。派生状态既可以在查询时实时计算,也可以在修改时同步维护。而 ensures 子句中的 \old 表达式进一步要求我必须明确"操作前的状态"和"操作后的状态"之间的差分关系——这实际上是在对代码做最精确的文档化。

这个阶段我认识到:规格驱动的架构设计是把"正确"写成了可检查的条件,而不是在限制自由度。 架构是"能不能满足规格"的问题,而不只是"好不好用"的问题。一种架构设计如果无法自然地映射到规格的后置条件,它就不是一个好的设计。

4.4 第四单元:从"实现驱动"到"模型驱动"

第四单元的任务是图书馆管理系统,核心要求是先建模(画类图)再实现,且类图与代码之间必须保持可追踪性。

在经历了第三单元的规格驱动开发后,第四单元的"模型驱动"带来了一个新的认知层次:架构设计是开发流程中的一个独立产物,而不仅仅是一个实现阶段的产物——它需要在写代码之前被审视、被评审、被检查。

这个阶段我的实践过程是:

  1. 读需求 → 画一阶类图 → 提交
  2. 画二阶类图(基于一阶精化)→ 写代码 → 调整二阶类图与代码一致
  3. 提交二阶类图 + 代码

在这个过程中,我发现自己的设计习惯发生了变化:以前是"先写代码,再考虑架构",现在是"先想架构,再写代码,写完再调整架构"。这个习惯的转变本身才是第四单元最大的收获。

五、总结自己在四个单元中测试思维的演进

5.1 第一单元:验证正确性

第一单元的测试主要是"构造输入 → 运行 → 对比输出"。测试方法比较朴素:构造一些多项式表达式作为输入,运行程序得到输出,与sympy的结果做对比。这一阶段我主要依赖用python写的评测机做整体化测试

5.2 第二单元:并发测试

第二单元引入了多线程,测试的复杂度急剧上升。

第一,确定性测试和随机测试的结合。 对于一些关键路径(如单电梯单请求、双电梯不交叉请求),我构造确定性输入序列,期望看到确定的输出。但对于并发场景,我开始使用随机输入生成器,构造大量随机请求序列,检测程序是否会出现异常(死锁、超时、状态不一致等)。

第二,引入时间维度。 多线程程序的行为与时间有关。我学会了在测试中关注"程序是否在合理时间内结束"、"所有请求是否都被处理"、"输出顺序是否合规"等时间相关的属性,而不仅仅是最终的数学正确性。

第三,压力测试。 第二单元的评测环境有着严格的性能要求。我构造了大量高并发请求的场景(短时间内大量乘客请求),观察程序是否会因为锁竞争、调度延迟、CPU 占用过高等原因超时。

但第二单元测试最大的局限性在于:并发 bug 往往不可复现。 同一个输入运行两次可能得到不同的结果,某些 bug 可能在压力测试中出现一次后就不再出现。这让我认识到测试在并发场景下的根本困境——测试只能发现 bug,无法证明没有 bug。

5.3 第三单元:基于规格的精确测试

第三单元的 JML 规格为测试提供了精确的 oracle。我不再需要手工计算结果来验证正确性,而是可以直接将规格中的 ensures 条件翻译为 JUnit 断言。

这个阶段的测试设计也比第一、第二单元更有系统性。我的方法变成了:

  1. 读 JML 规格,提取每个方法的 requiresensuressignals
  2. 先写异常测试(验证 signals);
  3. 再写正常测试(验证 ensures);
  4. 最后写状态不变性测试(验证 assignable 约束是否被遵守)。

测试变成了"先理解规格,再同时设计和测试",而非"先写代码再补测试"。

5.4 第四单元:行为级验证

第四单元没有 JML 规格,测试回到"构造输入 → 运行 → 对比输出"的基本模式。但经过前三单元的积累,我的测试思维已经发生了质的变化:

第一,从验证"对不对"到验证"是不是"。 在第一单元,测试的目标是"程序是否输出正确的计算结果"。在第四单元,测试的目标是"程序的行为是否符合需求文档中的每条规则"。这两者的区别在于:前者的错误可以直观看到(答案不对),后者的错误可能隐藏很深(如某个边界条件下信用分扣除不正确)。

第二,从构造单个测试到构造测试序列。 第四单元的业务逻辑是状态相关的——借书、还书、预约、取书、续借、扣信用分这些操作按照时间顺序组合,才能构成完整的业务场景。因此,测试设计也从"单个操作的正确性"扩展到了"操作序列的正确性"——一个操作的正确性可能依赖于之前若干操作的积累效果。

第三,状态覆盖而非路径覆盖。 在第四单元,测试的关注点从"代码执行了哪些分支"转向了"系统达到了哪些状态"。例如,书的流转状态(BOOKSHELF → BORROWED → RETURNED → BOOKSHELF)、用户的信用分变化(借书不逾期加 10 分、借书逾期扣 15 分等),关注的是"状态覆盖"而非"代码路径覆盖"。

5.5 测试思维的总体演进

回顾四个单元,我的测试思维大致沿着以下路径演进:

U1: 验证——程序是否正确处理了各种输入?
U2: 探索——程序在并发场景下会不会崩溃?
U3: 验证——程序的实现是否满足规格定义的全部约束?
U4: 验证——程序的行为是否满足需求文档中的业务规则?

从"写几个测试看看对不对"到"基于规格设计测试用例",再到"基于业务规则设计状态覆盖测试",测试的系统性在逐步提高。同时,我也越来越认识到测试的局限性——测试只能证明有 bug,不能证明没有 bug。在第三单元对 JML 和形式化验证的深入思考(尤其是对 Lean 的了解),让我意识到可证明的正确性和可测试的正确性之间的差距。


六、总结自己的课程收获

6.1 架构设计的系统性认知

这学期最大的收获是对"架构设计"建立了系统性的认知。在课程开始前,我对"架构"的理解基本停留在"把代码分成几个包、几个类"的层面。现在我的理解要深入得多:

架构是关于变化的应对策略。 一个架构设计的好坏,在于当需求发生变化时需要改动多少代码、涉及多少个类、改动的风险有多大,而不在于它写第一版时的整洁程度。回头看,四个单元中变化应对方式各不相同:

  • U1 用 Composite 模式把变化隔离在 Factor 接口的新实现中(增加 MyfuncFactor → ExponetFactor → DeriveFactor);
  • U2 用 Channel + ElevatorThread 的架构把变化隔离在线程内部,同时用 ElevatorShaft 在 HW7 引入新的抽象层级;
  • U3 用接口 + 实现分离的方式隔离变化(每次迭代新增规格接口,旧接口做 extends 扩展);
  • U4 用 Handler 模式把变化隔离在独立的 handler 类中。

这些模式虽然不同,但背后的思路是一致的:识别变化的维度,围绕变化维度设计隔离边界。

6.2 "设计的代价"意识

课程让我建立了"设计的代价"意识。每一个架构决策都有取舍:

  • 使用设计模式会增加抽象层数,能隔离变化但也会增加理解成本;
  • 拆分更多类能提高内聚性,但也会增加类之间的依赖复杂度;
  • 使用缓存能提高查询性能,但会带来一致性问题;
  • 做正向建模需要提前预判需求变化,但软件工程中最困难的事情之一就是预测未来。

做决策时要清楚代价是什么——这不是否定这些设计决策本身的价值。没有免费的抽象,也没有万能的设计模式。

6.3 写在最后:关于能力的成长

这学期最确定的感受是:我的"设计视界"被拓宽了。 课程开始前,我只能看到"这段代码能不能运行";现在我能同时看到"这个类之间的关系是否合理?这个设计能否应对未来的变化?这个规格的后置条件是否覆盖了所有路径?这些线程之间的共享状态是否最小化了?"

这种"多视角同时观察"的能力本身,就是这学期最大的收获。代码是否能运行只是一个维度,架构是否合理、设计是否可扩展、测试是否覆盖了关键路径、模型和实现是否一致——这些视角的叠加,构成了一个更完整的"软件质量"图景。而这些视角,是在四次迭代、十二次作业的逐步积累中一点一点建立起来的.

...全文
342 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

309

社区成员

发帖
与我相关
我的任务
社区描述
2026年北航面向对象设计与构造
java 高校
社区管理员
  • 孙琦航
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧