BUAA-OO-Unit 4: UML & 课程总结
总的来说这一单元过得比前三个单元轻松多了,感谢老师、助教对于期末的体谅。不过在第二次作业,由于粗心把续借的结果写成了从当前日期重新算时间导致强测错了两个点。还是不能飘……得仔细读题并检查!
除了难易程度外,第四单元相比前几单元有了更高的设计自由度,并且要求我们先设计UML图,再进行代码实现,让我体会到了正向建模与开发以及不用考虑性能因素写代码的快乐~
正向建模与开发
正向建模是指从高层次的抽象模型逐步生成具体的软件实现的过程。这一过程通常从需求分析开始,通过设计模型,最终生成代码实现。
需求分析
本单元作业模拟一个小型的图书管理系统,完成图书馆所支持的相关业务,均为功能需求。在白天开馆时需要接受用户的请求并处理,包括:借书,还书,查询,预约,预约取书,续借和图书漂流;闭馆后要进行图书整理,将借还处的书送回书架或者漂流角,将指定书籍移至预约处,将预约处逾期的书返回书架。
概念建模
阅读完作业指导书后,使用UML来创建系统的抽象模型。在作业过程中我主要用的是类图。
首先,对于书籍和用户各需要一个类来表示;其次,需要Main
类负责读取输入和输出,以及Controller
存储书籍、用户的信息,并进行各种操作;最后,由于预约处涉及到查询书籍是否过期的操作,决定将它单独列出一类AppointOffice
,其余书架、借还处、漂流角等则只在书籍状态中体现。
详细设计
接下来详细定义每个类的内部结构和行为,用到了类图和状态图(最后让画顺序图的时候框架已经定了,所以是写完代码才画的)。
对于Book
,首先需要LibraryBookId
类型的bookId
作为书号,以及int
类型的bookNum
表示有多少本。由于统一书号的副本之间在当前的需求中不需要区分,但又担心之后会,所以就折中用HashMap
存储各个副本的位置。该类的各个属性都写了外界可以访问的方法,以及外部命令书籍位置移动的方法。由于借还处体现在Book
的bookState
中,因此闭馆后的整理也需要Book
中的方法来配合,returnFromBro
在调用后会把该书号下位于借还处的副本送回书架或漂流角,并返回ArrayList<LibraryMoveInfo>
给Controller
。
对于Client
,属性需要id
;以及books
列表,在增添关于逾期的需求后,由列表变为HashMap
,存储书籍及其加入的时间;第三次作业中又增添了credit
属性。方法中包含对其属性的查询和更改。
对于Controller
,属性需要存储书籍、用户、预约处以及预约。方法在设计时主要每种请求对应一个方法来处理,依据输出是否需要,返回布尔值或者无返回内容。
其余两类相对简单,不再赘述。
代码实现
完成前期UML设计后,开始代码实现。在实现过程中还需要增添方法和属性,比如Controller
需要增添私有方法getBook
和getClient
以方便取出需要的书籍或用户;比如第三次作业加入了信用分系统后,在代码实现阶段发现一开始对于逾期归还的信用分设想有误:第一需要在过期当天扣除,第二要避免同一书籍重复扣除,因此在Client
类中增添属性mayOverDue
列表,存储可能会导致扣除信用分的书籍。
综上所述,在本单元的正向建模与开发中,我大致采用了这样的流程:
- 理解需求,并定下要建哪些类;
- 主要通过设计UML类图确定整体架构,状态图作为辅助;
- 依据类图进行代码实现,根据情况对类图进行更改。
架构设计
以下为最终作业的UML类图:
具体每个类的作用已经在前一部分介绍过。
对比类图和代码,类图中定义的属性和方法在代码中都有对应的实现,且访问修饰符也与类图一致。只要A类的代码中用到了B类,则在类图宗就存在关联关系(这次没有用到泛化和继承),具体是哪一种关联关系则对照单元手册去逐个分辨(还不是非常熟练)。
对比状态图和代码,书籍的每一种状态均在enumeration``BookState
中,书籍状态转换的trigger
都间接调用了Book
中改变状态属性的方法。至于Guard
感觉好麻烦就没写……(实在是不应该)
对比顺序图和代码,顺序图呈现的内容是预约成功后取书的流程,其生命线的名称均为代码中的类名(前面加冒号),消息均为代码中的方法,且该方法必存在于消息的接受者或者发出者中。
综上,代码实现基本上可以很好地反映 UML 模型的设计意图。UML 模型为系统设计提供了清晰的蓝图,而代码实现则是对这一蓝图的具体落实。
架构设计思维的演进
通过四个单元的学习,我逐渐接触了Java
的架构设计,意识到了其重要性并努力写出“漂亮”的代码框架。具体可分为以下几点:
SOLID原则
SOLID 原则是面向对象设计和编程中一组被广泛认可的五个原则,包括单一职责原则、开放封闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。其中对于第一个原则我的体会最深,在第一单元中,Operator
类的RFC值较高,在代码中被反复调用,由于我一边存储一边计算,导致面对指数函数等的化简非常棘手以致于摆烂;而且由于开始时没有命令-查询分离原则导致出现bug修复了很久。这些都是由于我没有将职责分清楚导致的,一个类、一个方法都应只承担一个职责,这样它的变化只能由一种类型的需求改变引起,可以降低类的复杂度,提高其可维护性。
设计模式
在第二单元中,我在指导书的引导下应用了生产者-消费者模式,也了解了单例模式、工厂模式、策略模式、观察者模式等等。这些前人总结好的设计模式是真的优美!为这一单元的作业提供了经过验证的解决方案。这些设计模式遵循了面向对象设计原则,可以帮助我们编写高质量的代码,构建更加可靠和高效的系统。
层次化设计
在第三单元和第四单元的架构中,类大致都分为三类:负责接受输入的数据并调用方法,负责处理具体二点业务逻辑和规则,和基础的数据类。层次化设计通过将系统划分为若干逻辑层,每一层负责特定类型的任务或功能。这种设计方法可以增强系统的模块化、可维护性和可扩展性。每个类也就自然做到了单一职责原则。
系统架构设计
经过前两个单元、尤其是第一单元重构的惨痛教训,再加上第四单元的强化训练,让我深刻意识到了在写代码前做好架构设计的重要性。也通过研讨课理解了系统架构设计和敏捷开发两者并不冲突。凭目前了解感觉系统架构设计和正向建模与开发好像差不多(?),所以这里就不重复了。
测试思维的演进
这四单元我除了第一次作业自己写了测试代码外,其余主要依靠其他人搭建好的。前期测试时主要依靠随机数据,后来体会到了边界值测试和压力测试的重要性(比如第二单元的“围师必阙”和第三单元的int
边界值)。
通过第三单元的学习,让我了解了黑箱测试、白箱测试、单元测试等诸多概念。编写 JUnit 单元测试也是一次很有趣的体验。规格信息明确描述了方法的行为、输入、输出和预期效果。通过阅读和理解规格信息,我们可以清楚地知道方法应该完成什么任务,应该返回什么结果,以及在什么条件下应该抛出哪些异常。可以逐行验证代码是否满足了规格要求,包括/@ pure @/等。在实现JUnit测试时,要尽可能根据规格信息设计全面且有效的测试用例,包括正常情况测试、边界条件测试和异常情况测试;从而确保方法实现满足功能需求,验证实现的完整性和正确性,促进了规格与实现的分离。
简而言之,OO的学习让我明白了在写完程序后进行测试的必要性,也初步了解了各种测试的方法和思路。
课程收获
感谢认真负责OO课程组一个学期以来的辛勤工作!也非常感谢这一学期帮助过我的同学!!尤其感谢wxm带我入门,让我在之前没有接触过Java的情况下也完成了这门课。
通过本课程的学习,让我学会了Java的基本语法,培养了面向对象编程的思维,在架构设计思维、测试思维等方面实现了从0到1的突破。