272
社区成员




正向建模是一种软件工程方法,它将系统建模为由功能元素组成的层次结构。 每个层次的功能被分割成子集,在每个子集中,元素之间存在着逐步的细化和分解关系,从而形成了系统框架。 正向建模方法是一种自上而下的设计过程,它从高层次的总体功能出发,逐步细化到低层次的细节设计,以实现对整个软件系统的全面把握。
首先,我们要对指导书进行需求分析
我们要明白需求后,去绘制UML图
类图:是面向对象系统建模中最常用和最重要的图,是定义其它图的基础。类图主要是用来显示系统中的类、接口以及它们之间的静态结构和关系的一种静态模型
状态图:是一种行为图,描述一个特定对象的所有可能的状态以及引起状态转换的事件。
顺序图:顺序图常用来描述用例的实现,它表明了由哪些对象,通过消息相互协作来实现用例的功能,在顺序图中,标识了消息发生交互的先后顺序。
最后,根据UML图去写代码
根据书籍存放的位置,我建立了以下几个类去实现核心 Library
类的统一调度和管理。
BookShelf
:书架,管理书籍的库存信息,提供添加、查询和借阅书籍的功能。
ReadingRoom
:阅览室,管理正在阅读的书籍信息,提供添加、移除和清空阅读书籍的功能。
Ao
:预约处,管理书籍的预约信息,提供添加、查询、移除和替换预约的功能。
我用Book
表示图书馆中的一本预约的书,包含书籍的基本信息,如学生 ID、ISBN 号、书籍 ID 和过期日期。
Bro
:借还处,管理被扣在此处的书籍信息,提供添加和查询书籍的功能。
User
:用户,管理用户的借阅信息和信用分数,提供借阅、归还、检查逾期和更新信用分数的功能。
Library
是图书馆的核心管理类,负责处理各种图书馆操作,如开馆、闭馆、借阅、归还、预约、查询等。我在Library
类中实例化了上述几个类
private final BookShelf bookShelf;
private final BookShelf hotShelf;
private final Ao ao = new Ao();
private final Bro bro = new Bro();
private final ReadingRoom readingRoom = new ReadingRoom();
private final HashMap<String, User> users = new HashMap<>();
private final HashMap<LibraryBookId, LibraryBookState> bookStates = new HashMap<>();
private final HashMap<LibraryBookId, List<LibraryTrace>> bookTraces = new HashMap<>();
private final HashSet<LibraryBookIsbn> hotBooks = new HashSet<>();
通过bookStates
记录书籍的状态,bookTraces
记录书籍的移动路径,hotBooks
记录当前的热门书籍
每个操作都有单独对应的函数
switch (type) {
case BORROWED:
library.borrow(today, bookIsbn, req);
break;
case RETURNED:
library.returnBook(today, req.getBookId(), req);
break;
case QUERIED:
library.query(today,req.getBookId());
break;
case PICKED:
library.pick(today,bookIsbn,req);
break;
case ORDERED:
library.orderNewBook(bookIsbn,req);
break;
case READ:
library.read(today, bookIsbn, req);
break;
case RESTORED:
library.restore(today, req.getBookId(), req);
break;
default:
break;
}
MainClass
、Book
、BookShelf
、ReadingRoom
、Library
、Ao
、User
和Bro
类都在 UML 模型中有相应的表示。Library
类中的sortBooks
、borrow
、returnBook
等方法,以及User
类中的borrowBook
、returnBook
、checkOverdue
等方法都在 UML 模型中有相应的定义。最终的代码设计与 UML 模型设计之间具有较高的一致性,说明在设计阶段对系统的架构和功能有清晰的规划,并且在实现过程中能够按照设计进行编码。
在利用解决问题时,不要想一步直接得到完整答案,可以引导ai一步一步给出答案,保证不会漏掉一些细节性的东西。我把指导书内容发给ai,让他帮我分析一下本次作业需要哪些类来完成,每个类需要哪些属性,需要构造哪些函数。它的初始架构会有一些不合理的地方,给出框架不一定能完全符合我对代码的设计,所以仍需要去修改。通过对它分的类的仔细思考,我指出了一些设计不合理的地方,最终得到了一个架构。我再大体上以这个架构为基础进行编码,细节上根据题目意思和我的构想进行了修改。
在实现了所有功能之后,我让ai分析可能出错的情况,我对照可能的错误项检查了我的代码。当遇到错误时,可以将输入与输出告诉大模型,大部分情况下,大模型会比较精确的告诉你问题出在哪,进而提升代码正确性。
在这门课之前我只会单纯把所有要求丢给ai,然后告诉它写错了这种特别基本的用法,但是在经过大模型高效用法的学习后,我知道该如何与大模型正确沟通,我们要引导大模型去解决问题,而不是简单的把所有要求丢给他。
我们要一步一步引导大模型:
提取题目中内容完成了类的提取之后,接下来我们要完成类之间关系的提取。我们推荐将“提取关系”这一大任务拆分成“提取xx关系”等几个子任务,针对不同关系设计不同的输入
第一单元内容是表达式展开,这个单元锻炼了我们面向对象的思维。多项式是对象,通过一个个单项式连接,单项式也是对象,单项式由一个个因子组成,因子也是对象,因子有很多不同的种类,这些不同种类的因子都是对象。我将Factor设计成了一个接口,用它来统一管理Num
, Var
, Sin
, Cos
, Func
, Dx
等不同种类的因子。我将所有的因子都转换成多项式,多项式转换成单项式进行统一的计算。整个代码我并没有重构过,OO公众号一开始给了我们很好的框架与思路,照着那个思路写就不会有什么大问题。因为这个时候对OO的内容还比较陌生,只顾着完成代码就已经很难了,所以在第一单元中我对面向对象的思想并没有很注重,最后代码耦合程度较高。
第二单元内容是电梯调度,涉及到多线程的创建和维护,同时还要注意是否出现轮询和死锁这样的问题。由于多线程的结果具有不可复现性,所以对bug的定位和修复也变得格外困难,这都对我们编写多线程程序提出了更高的要求。在第二单元中我使用了生产者-消费者模式、单例模式等,让各个类的职责更加清晰和单一了。层次化设计的关键我认为是,明白你要处理的对象。在写代码前你需要梳理清楚你究竟要处理哪些对象,每个对象你希望它完成什么目标。我在这个单元的完成中实现了真正的纯电梯类,即只保存电梯基本信息和做简单的基本操作,减少了Elevator类构造的复杂度。
第三单元内容是JML规格,我们要理解JML语言,基于规格进行代码实现。与前两个单元自己设计架构不同,本单元考察的是阅读已有架构的规格并加以实现的能力,不需要我们去想代码框架,要设计哪些函数,只需要根据提供的规格进行编程。虽然代码规格已经给定,但具体的实现却有许多方法,这次作业对性能的要求也很高。经过本单元的训练,我了解了更多规格方面的知识,也对一些图论算法有更深刻的掌握,虽然此次作业我并没有用并查集,但也在编写代码过程中对这一部分内容进行了学习。我主要使用了BFS算法,将之前学到的知识又用到代码中。
第四单元的主题是”UML建模语言“,我们需要掌握正向建模与设计的思想,用UML图辅助指导代码设计。在这个单元的学习中,我了解了提前设计的重要性,一个优秀的架构能让代码的可维护性和扩展性更高。先有UML设计,用大体上的UML图指导代码设计,再通过代码去完善UML图。直接写好UML是很困难的,需要基于代码去反复修改UML图。
在第一单元中,阅读指导书后需理清楚这次作业有哪些需要测试的要点,并以这些要点为类去构造样例。导数(数字、变量、三角函数、自定义函数),自定义函数(普通自定义函数、双重自定义函数 ),递推函数(含三角函数的递推函数、含自定义函数的递推函数),我们需要考虑周全,对这几类情况分别测试。此外,还需注意边界性测试,BigInteger,多层嵌套,CPU运行时间等,这些样例不好构造,可借助自动化生成数据的代码。
在第二单元中,光有评测机测评是可以测出错误的,但本单元难的不是找到错误,而是通过复现错误去精准定位错误所在。线程安全问题光靠看代码是没有办法找出bug的,我在debug的过程中最多使用的就是加printf
,把线程的状态通过输出去展现。我是会用有问题的那个数据多跑几遍,当发现问题时,对那个电梯的操作一步步看,看是从哪一步开始这个电梯进入了死循环,然后在进入死循环的那几个判断中加上输出,找到究竟是哪一个部分判断有误。
在第三单元中,我是编写Junit进行单元测试,在写完一个功能的方法后就对其进行测试。数据构造的要求是要全面,根据JML构造数据,覆盖normal_behavior和exceptional_behavior。我采取的策略是随机生成,同时为了确保又对其进行分类,分为没人、有人没关系、有人有关系三类。在设计JUnit测试时,可以针对这些边界条件编写测试用例,以确保代码在边界情况下的正确性。比如,规格说明一个数组的索引范围是0到100,那么除了正常的索引值测试外,还应测试索引为0、100以及-1、101等边界和越界情况。我们需要保证在测试中能包含明确前置条件、验证后置条件、覆盖边界条件、验证不定式、处理副作用5个方面,这样才能将规格信息测试完全,其中验证JML语言的pure属性是很重要的一环。
在第四单元中,本单元的测试还是十分简单的,我使用的是评测机去测试。
在这一学期的OO课程中,我学会了很多知识,递归下降、多线程、JML、UML,也将这些知识熟练用到实践中。我更加了解IDEA这个软件,学会了其中很多插件的用法,也在一次次代码设计的过程中理解面向对象的思想。八次实验、十二次作业,让我的代码能力有了巨大的提升,在不知不觉中,我已经能独立完成几千行代码的作业了。在一次次测试过程中,也让我体会到代码正确性才是最重要的,有时候为了提升性能可能还会导致代码出现bug,才是得不偿失。荣老师课上也提到不要为了代码性能降低代码的可读性,这对我们之后的设计工作有很大的帮助。
这一学期的OO课程对我的心态也是一个磨砺,从一开始第一单元的崩溃到第二单元的不知如何下手,我感觉压力山大,直到第七次作业,我才终于慢慢适应这么课的节奏,写代码时才更得心应手。我学会了在高压下调整心态,不要把后面的压力强加在现在的自己身上;合理分配自己的时间,不要熬夜太晚,会影响第二天学习的状态和效率。学习到后面,我发现这门课注重的不只是代码能力的提升,更多的是提升对代码的处理能力,包括读懂要求,给出设计图等,真正的带领我们去感受面向对象的思想。
感谢课程组全体成员对课程的付出!