正向建模与开发
正向建模与开发是指从需求分析开始,通过逐步细化需求,创建模型,并最终开发出软件的过程。这种方法强调从抽象到具体、从概念到实现的系统性过程。
而本单元正式所使用 UML(Unified Modeling Language
)建模语言则是贯彻上述理念的一个很好的工具。在之前几个单元的设计中,我们虽然已经开始对 UML 有一些接触,但只是局限于用它来对每单元最后的迭代结果进行总结分析。既然有没有 UML 我们都或好或坏地完成了架构设计任务,那为什么还要花时间来接触这样一门建模语言呢?
面向对象本质上定义了一个抽象语言系统
- 词汇:对象、属性、操作、活动、流程、状态……
- 语法:对象间连接、对象与数据间连接、对象与操作间连接、属性与操作间连接、属性与活动间连接、操作与状态间连接……
我们作为面向对象的初学者,通过 oopre + oo 算是品尝了一番迭代开发的滋味,也许不必在意用这样的语言组成的话语会向别人传达什么信息,只要自己代码写得痛快并且能够理解就行。但也许未来我们可能会面对越来越复杂、迭代不止的软件开发任务,或者与他人进行合作开发;这个时候若还是看了眼需求埋头就写、边想边写,就有点不太现实了。
UML 作为一种建模语言,它提供了一种标准的、统一的、可视化的建模方法,使得我们可以用一种统一的语言来描述软件系统的各个方面,从而使得我们能够更好地理解、分析、设计、实现、测试、维护软件系统。正向建模与开发就是一种系统化的软件开发方法,通过需求分析、系统建模、架构设计、详细设计、编码实现、集成测试和部署维护等步骤,确保软件开发过程的规范性和可控性。这种方法有助于提高软件开发的效率和质量,降低项目风险。
不过,很多时候在一开始就通过建模语言设计好所有的架构是不现实的,因为需求总是会变化的。所以在实际开发中,我们可以采用增量式开发的方法,先设计好一个基本的架构,然后根据需求的变化逐步迭代,逐步完善。这样可以在保证软件质量的同时,也能够更好地适应需求的变化。而在这单元的实践中,我主要采用的是先用语言文字进行简单建模,然后再上手进行对应代码的具体实现。
Unit4架构设计
本单元的主要任务是模拟一个读书馆的管理系统,我们需要设计一个系统,能够管理读者、图书、借阅记录等信息。
最终架构
类图
状态图(书籍)
顺序图(预约)
迭代过程
三次简易文字建模如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| ## hw13
1. close后整理 - 借还处的书全部放回书架 - 预约处的过期(对预约的所有人)的书放回书架,删除预约表中记录
2. open后整理
- 根据预约表和书架的情况取书到预约台,为指定用户保留五天并删除预约表中记录
3. 开馆
- 借阅 - 先判断是否可从书架中取出 - 再根据用户持书情况判断此书的归属 - 还书 - 本次作业还书立即成功,书由用户$\to$借还处 - 预约 - 根据持书情况判断是否允许该预约,若预约表中已有则拒绝这次预约 - 若预约成功,预约请求加入预约表
- 取书 - 预约处查询,若为其保存且是要取的书,则取书成功,删除预约表对应记录 - 否则失败 - 查询 - 直接查询书架情况即可
## hw14
0. - 正式书籍:书架、预约处、借还处、用户 - 非正式书籍:借还处、漂流角、用户 - 不可预约、续借 - 由用户捐献,序列号与书架不同,且书号不重复(最多一本) 1. close后整理 - 借还处的书根据不同情况全部送回(书架、漂流角) - 预约处的过期(对预约的所有人)的书放回书架,删除预约表中记录 2. open后整理 - 预约处的过期的书放回书架 - 根据预约表和书架的情况取书到预约处,为指定用户保留五天并删除预约表中记录 3. 开馆 - 查询 - 根据`Type`查询书架或漂流角 - 借书 - 先判断是否可从书架中取出 - 再根据用户持书情况判断此书的归属,借书成功时设定还书期限 - 预约 - 非正式书籍不可预约 - 根据持书情况判断是否允许该预约,预约成功则预约请求加入预约表 - 还书 - 还书立即成功,需要判断是否逾期 - 取书 - 预约处查询,若为其保存且是要取的书,则取书成功,删除预约表对应记录,确定还书期限 - 否则失败 - 续借 - 还书期限前5天内才可续借,若有对该书的预约且书架上无书则失败 - 续借成功则还书期限延长30天 - 捐献 - 一定成功,直接放入漂流角
## hw15
0. - 新增用户信用分(信用分可查询) - 初始为10,上限20 - 信用分为负,不允许借书(扣在借还处)、预约、续借 - 按时还书+1,逾期后**立刻**-2 - 未按时取书-3 - 捐赠图书+2,变为正式书籍再+2
|
第一次设计时就采用了将管理操作分为开馆、闭馆、日常操作三个部分,并且使用单例模式来实现对于图书馆的管理。图书馆中包括书架、借还处、预约处以及用户数据等信息。设置Appointment
类记录单次预约信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public class Library { private static Library library; private final BookShelf bs = new BookShelf(LibrarySystem.SCANNER.getInventory()); private final BorrowAndReturnOffice bro = new BorrowAndReturnOffice(); private final AppointmentOffice ao = new AppointmentOffice(); private Library() {} public static Library getInstance() { if (library == null) { library = new Library(); } return library; } public void run() { while (true) { LibraryCommand<?> command = LibrarySystem.SCANNER.nextCommand(); if (command == null) { break; } if (command.getCmd().equals("OPEN")) { open(); } else if (command.getCmd().equals("CLOSE")) { close(); } else { process((LibraryRequest) command.getCmd()); } } } private void open() { } private void close() { } private void process(LibraryRequest request) { } }
|
其实从文字版建模可以看出,我对预约请求的处理由“不许重复预约”(此时题目要求没有对管理策略进行限制)变化为“只要条件满足就允许”,也算是根据需求的变化而不断调整架构设计的:一个写照了。
其他的内容就是不断添加新的功能,比如捐赠、图书升级、续借、信用分等,然后在原有的基础上新增漂流角、进行修改。
比如对User
类的属性进行了适当的修改:
1 2 3 4 5
| private final String id; private final Map<LibraryBookId, Integer> books; private boolean bookB; private boolean bookBU; private int credit;
|
并增加续借、信用分等的处理:
1 2 3 4 5 6 7 8 9 10 11 12
| public int renew(LibraryBookId bookId, int day) { int returnDue = books.get(bookId); books.put(bookId, returnDue + day); return returnDue; }
public void changeCredit(int change) { if (credit + change > 20) { credit = 20; } else { credit += change; } }
|
给预约请求类Appointment
增加order
属性以决定分配书籍顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Appointment { private static long count = 0; private final long order; private final User user; private final LibraryBookId bookId; public Appointment(User user, LibraryBookId bookId) { this.order = count++; this.user = user; this.bookId = bookId; } }
|
在开馆前整理时,对预约请求进行处理:
1 2 3 4 5 6 7
| List<Appointment> tmp = new ArrayList<>(requestSet); tmp.sort((o1, o2) -> { if (o1.equals(o2) || o1.getOrder() == o2.getOrder()) { return 0; } return Long.compare(o1.getOrder(), o2.getOrder()); }); tmp.removeIf(...);
|
新增属性returnForm
来判断每天闭馆时哪些图书逾期,以修改对应用户信用分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| private final List<Map<String, Integer>> returnForm = new ArrayList<>(500);
private Library() { for (int i = 0; i < 500; ++i) { returnForm.add(new HashMap<>()); } }
private void open(LocalDate date) { for (int day = this.date + 1; day < date.getDayOfYear(); ++day) { returnForm.get(day).forEach((key, value) -> users.get(key).changeCredit(-2 * value)); } }
private void close(LocalDate date) { returnForm.get(this.date).forEach((key, value) -> users.get(key).changeCredit(-2 * value)); }
|
思维演进
架构设计
从先导课到如今 oo 正课迎来尾声,从开始时对 Java 编程的学习,到体会和学习面向对象的设计思想,我的确收获颇深,也算是和课程的名字(面向对象设计与构造)相符了吧。
在 Unit1 中,我接触到了层次化设计的思想,学习了递归下降这样的实践方法。除了重新捡起先导课 Java 语法的知识,抽象问题的能力和架构设计的感觉也逐渐从无到有地发展起来。本学期的唯一一次大重构也发生在这个单元。总的来说,这单元的学习给我打开了面向对象设计的大门,让我了解和学习了许多结构和层次的思想。
在 Unit2 中,接触到了多线程设计的问题。同时这一时期,操作系统课也了解了些许相关的知识。不过由于是第一次建立起对多线程运行机制的认识,这单元的迭代作业可谓是至暗时刻。在无数次 debug 以及动手实现影子电梯之后,终于在这个单元快要结束的时候,我才对各个线程和类之间如何交互、如何保证安全性有了很清晰的认知,只是可惜两次作业中的影子电梯没能检查出所有的线程安全问题、没能安全通过这个单元,也算是一个遗憾了。
在 Unit3 中,接触到了契约式设计,感觉这个思想还是挺好的,然后也学到了 JML 对于 JUnit 测试的帮助。
在 Unit4 中,则是接触了正向建模开发这一思想。在这单元的迭代作业中,使用的文字版建模让我很快完成了每次任务。在写代码之前进行总体的架构设计、并在实现过程中不断改进,确实是提高效率、减缓代码屎山化的不二选择。
测试
很惭愧,这个学期没有搭建一个自己的评测机,大部分时候是白嫖其他同学的评测机,然后自己再手搓数据进行压力测试、反复阅读指导书和代码测试 Corner Case 。
学习 oo 之前对测试的认识就是简答地查看测试用例的结果是否正确,但之后发现测试是很丰富的,包括主要接触到的黑箱测试、白箱测试,以及单元测试、集成测试、系统测试等等。感触最深的还是 Unit3 对于 JML 规格的学习,这种测试方法在契约式设计时对于代码的正确性检查应该是很有帮助的。
课程收获
不管怎么说,还是顺利结束了这门声名远扬的课程的学习。虽然感觉最后没有实现幻想中的完全胜利,也没有像一些同学收获那么多,但还是学到了一些或感兴趣或有用的知识和思想——层次化设计、多线程、契约式设计等等。虽然熬夜重构第二次作业时因拙劣的架构和不熟悉的语法特性而破防,虽然费了很多心思写的影子电梯还是被测出了 bug ……但更能抗压了,更能 debug 了。如果重新来过可能结局会好那么一点,不过过程总是这样的。失败总是贯穿始终,遗憾才是常态,就这样吧。