BUAA OO第四单元总结

曲赫-23230608 2025-06-12 17:49:38

一、 正向建模与开发实践总结

本单元的核心是“正向开发”,即从需求分析开始,通过UML进行系统建模,最后根据模型编写代码。这一过程与我们之前“拿到需求直接开干”的模式截然不同。

1. 建模过程回顾

我的开发流程大致如下:

  1. 需求分析:仔细阅读指导书中的文字描述,这是所有工作的基础。我会将关键的业务规则、实体、操作都摘录出来。

  2. 静态结构建模 (UML类图)

    • 识别实体: 从需求中识别出核心的名词,如“图书馆 (Library)”、“书籍副本 (Book)”、“学生 (Student)”、“书架 (Bookshelf)”、“预约处 (AppointmentOffice)”等。这些自然地成为了我们系统中的类。

    • 定义属性与关系: 分析每个实体的属性(如Book有ID、状态;Student有姓名、已借阅列表)和它们之间的关系。例如,Library“拥有”BookshelfAppointmentOffice等,这是一种强烈的“组合”关系;而 Bookshelf “包含”多本 Book,这是一种“聚合”关系。这些关系直接指导了代码中成员变量的设计。

  3. 动态行为建模 (UML状态图与顺序图)

    • 状态图: 对于像Book这样拥有复杂生命周期的核心对象,我为其绘制了状态图。一本书的状态从“在书架上” (bs/hbs),到被预约后移至“预约处” (ao),再到被学生取走成为“用户持有” (user),归还后进入“借还处” (bro),最后在整理日回到书架。这个过程中的每一个状态转换(如borrowed, returned, picked, restored)都对应了代码中的一个具体方法或逻辑分支。

    • 顺序图: 对于复杂的用户场景,如“学生预约并成功取书”,我会绘制顺序图来梳理对象间的交互流程。例如,Main接收到ordered指令,传递给LibraryLibrary调用Student检查资格,然后更新OrderManager(如果将其分离),最后在整理日,Library协调BookshelfAppointmentOffice完成书籍的物理移动。这清晰地定义了方法调用的顺序和依赖关系。

2. 正向开发的优势

通过这种自顶向下、从模型到代码的开发方式,我深切体会到其带来的好处:

  • 结构清晰:在动手编码前,整个系统的宏观架构和核心对象的职责已经非常明确,避免了“代码写到一半发现结构不对推倒重来”的窘境。

  • 逻辑完备性:建模过程强迫我们思考各种边界情况和状态转换的触发条件,减少了在实现阶段的逻辑遗漏。

  • 易于协作与沟通:UML作为一种图形化的标准语言,提供了一个清晰、无歧义的沟通媒介。

二、 架构设计与UML模型追踪关系分析

本单元的三次作业在功能上层层递进,从一个基础的图书馆,到增加了热门书架和阅览室,我的架构也随之演化。

1. 最终架构设计

最终形成了一个职责分明的架构,这与我最初的UML模型高度对应:

  • Main.java: 程序入口,负责接收官方IO的输入,并将指令派发给Library对象。

  • Library.java: 系统的“大脑”和总协调者。它不直接处理具体的位置管理,而是持有各个功能区的管理器实例,负责解析指令、协调各组件完成复杂的业务流程(如开闭馆整理)。

  • 实体类:

    • Book.java: 封装了单个书籍副本的所有状态和属性。

    • Student.java: 封装了学生的状态,如图书持有情况、预约状态、阅读状态等。

  • 位置管理器类:

    • Bookshelf.java: 管理所有在书架上的书(包括物理上的普通和热门书架)。

    • BorrowReturnOffice.java: 管理借还处的书籍。

    • AppointmentOffice.java: 管理预约处的书籍。

    • ReadingRoom.java: 管理阅览室的书籍。

  • 服务类:

    • TraceManager.java: 专门负责记录和提供所有书籍的移动轨迹。

这个架构遵循了“单一职责原则”,每个类都只做一件事,使得代码逻辑清晰,易于扩展和维护。

2. UML模型与代码的追踪关系

  • 类图 -> 代码:

    • UML类图中的每一个类(Library, Book, Student等)都直接对应一个.java文件。

    • 类图中的属性(如Bookid, currentState)对应了类中的成员变量。

    • 类图中的关系在代码中得到了体现:

      • 组合关系: Library 类中持有 Bookshelf, BorrowReturnOffice等实例,体现了强烈的“拥有”关系。

      • 聚合关系: Bookshelf 类中持有一个 Map<LibraryBookId, Book>,体现了书架“包含”书籍的关系。

      • 依赖关系: Book类的setCurrentState方法需要传入一个TraceManager对象来记录轨迹,这体现了BookTraceManager的依赖。

  • 状态图 -> 代码:

    • Book的状态图为例,currentState这个LibraryBookState类型的成员变量就是状态图的直接实现。

    • 状态图中的每一个事件 (Event),如borrowedreturnedpickedrestored,都对应Library类中的一个handle...方法。

    • 这些方法内部的逻辑,就是对**守护条件 (Guard)的判断(如if (!isLibraryOpen || isbn.isTypeA() || ...))和对动作 (Action)的执行(如book.setCurrentState(...), student.addBorrowedBook(...))。

  • 顺序图 -> 代码:

    • 以“学生成功阅读一本书”为例,其顺序图可能描绘了Main -> Library -> Student -> Bookshelf -> ReadingRoom -> TraceManager之间的消息传递。

    • 这在代码中就体现为Main调用library.processCommand(),然后library.handleRequest()调用handleRead(),在handleRead()内部,会依次调用student.canReadToday()进行检查,调用bookshelf.findAvailableCopyForReading()查找书籍,成功后调用bookshelf.removeBook()readingRoom.addBook(),并最终通过book.setCurrentState()调用traceManager.addTrace()。代码的调用栈清晰地反映了顺序图中的交互时序。

三、 如何引导大模型在复杂场景中完成架构设计任务

本次作业我借助了大模型的帮助,这个过程让我对“AI编程”有了更深刻的认识。

引导AI进行架构设计的有效策略

  1. 顶层设计:由人来定义系统的宏观架构、核心模块及其职责。向AI提出类似“我们需要一个A类、B类、C类,A负责...,B负责...”这样的高级指令。

  2. 将需求转化为具体规则:将复杂的自然语言需求,拆解成清晰、无歧义的逻辑规则点,逐一喂给AI。

  3. 提供精确的反馈循环:使用“输入-你的输出-我的期望输出-差异点”的模式进行反馈。这是最有效的调试方式。

  4. 将UML作为沟通语言: 在更复杂的项目中,可以先让AI生成初步的PlantUML代码,然后由人来修改和确认UML模型,这能最大程度上保证架构的正确性。

 

四、四个单元中架构设计思维的演进

  • 第一单元(表达式解析):从面向过程到层次化设计

  • 起初,面对复杂的表达式字符串,我的第一反应是“如何用一个复杂的正则表达式或一个冗长的方法解决所有问题”。这是一种典型的面向过程思维,试图用一个“万能函数”包揽一切,导致代码耦合度高,难以扩展和维护。在重构和迭代中,我被迫将表达式拆解。“递归下降” 的思想成为了我的启蒙。我学会了将“表达式”分解为“项”,将“项”分解为“因子”,并为每一个层次建立对应的类(Expression, Term, Factor)。这种层次化、对象化的拆分,使得每个类的职责变得单一。

  • 第二单元(多线程电梯):从静态结构到动态交互

  • 进入多线程单元,我的思维惯性还停留在第一单元的静态数据结构上。我首先想到的是如何设计“电梯”这个类,而忽略了“请求”和“调度”的动态特性。真正的挑战在于如何处理多个独立运行的线程(电梯、请求放入者)之间的协作与资源争抢。我的架构核心从设计单个类,转向设计线程安全的共享对象和线程间的交互模式。“生产者-消费者”模式成为了整个架构的灵魂,我设计了中央的“调度器”(Scheduler)作为“托盘”,所有请求者(生产者)向其中放入请求,所有电梯(消费者)从中获取请求。这让我认识到,好的架构不仅要设计优美的静态类图,更要规划清晰的动态协作流程。

  • 第三单元(JML规格):从自由创造到“契约式设计”

  • 在前两个单元,架构设计是自由的,目标是“解决问题”。面对JML,我最初把它当成一种“带注释的代码”,试图先写代码再去匹配JML。很快我发现这是本末倒置。JML的核心是“契约式设计”(Design by Contract)。我的思维从“我该怎么实现?”转变为“我必须遵守什么?”。架构设计不再是天马行空,而是基于JML接口进行“填空”。我开始首先深入理解JML描述的requires, ensures, assignable等条款,思考如何选择最优的数据结构(如用HashMap优化查询)来满足规格所要求的性能,并时刻维护数据不变量(invariant)。这次演进让我懂得了接口与实现分离的深刻含义,架构的首要职责是定义和遵守契约,而非沉迷于实现细节。

  • 第四单元(UML建模):从代码驱动到模型驱动

  • 前三个单元的训练,让我习惯于直接面对代码和需求。UML图在初期对我来说,只是“代码的另一种表现形式”,是写完代码后补充的文档。本单元要求我们进行“正向建模”彻底颠覆了我的开发流程。我学会了先用类图设计系统的静态骨架,用顺序图推演关键业务逻辑的交互流程,用状态图明确核心对象(如图书、借还机)的生命周期。我的设计工作从IDE转移到了建模工具。代码不再是设计的起点,而是高层模型的实现产物。这种从抽象模型到具体代码的“降维打击”,让我在编码前就对系统全貌了然于胸,极大地减少了后期重构的风险。

五、测试思维的演进:从“功能验证”到“多维保障”

如果说架构设计是“建高楼”,那么测试就是“做质检”。我的测试思维同样经历了一场深刻的变革。

  • 第一单元(表达式求导):从手动验证到自动化评测

    • 最初的测试就是“人眼编译”,手动输入几个简单的样例,如x*x, sin(x),看看结果对不对。随着表达式嵌套加深、空白符增多、出现非法格式,手动测试变得捉襟见肘。我开始编写Python脚本,自动生成海量、复杂的合法及非法测试数据。这让我体会到自动化测试和数据生成器的威力。我的测试思维从“点”的验证(几个特定样例)扩展到了“面”的覆盖(覆盖各种边界和异常情况)。

  • 第三单元(JML规格):从黑盒猜测到规格驱动

    • 在没有JML的时代,测试用例的设计很大程度上依赖于对代码实现的“猜测”,属于“灰盒测试”。我的测试思维转变为规格驱动测试。每一个JML方法都是一个测试单元,requires子句定义了测试输入的边界和前提,ensures子句定义了期望的输出和状态变化,signals子句则明确了异常情况的测试。我系统地针对每个方法的每个条款编写JUnit单元测试,这是一种严谨的、可追溯的测试方法,确保了实现与规格的完全一致。

结语

OO课程带给我的不仅是知识,更是一种结构化、系统化、抽象化的思维方式。这种思维的锤炼,将是我未来面对任何挑战时,最宝贵的财富。

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

272

社区成员

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

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