BUAA2024OO 第四单元博客总结

前言

​ 本次作业为2024年OO课程的最后一次作业,作为6系能够让人涅槃重生的几门大课重课之一,其含金量自然不用多说。回首之前经历的四次迭代开发和数不清的debug的日夜,在痛苦之余,也有不少收获。于我个人而言,不仅仅是技术上收获,更有对学习思想和开发测试思维的锻炼。

本单元的正向建模与开发

​ 本单元的指导书中相比于前面三个单元尤其是前两个单元的指导书相比,最大的一点不同在于,其只对规则做出限制,而不规定具体实现。

​ 一方面,其不再规定具体实现,给我们的自由发挥带来了一定的空间,但同时,要在没有具体方向的指引的情况下写出符号要求和规则的代码实现也并非易事。

​ 由此,正向建模的好处便显现出来,本单元的关键词是UML,作为一个统一建模语言,其可以对我们要实现的类、类与类之间的关系、类的职能、状态转移、事件时序等等的不同维度对目标进行建模,在我们动手写代码之前,先利用UML进行建模,修正完善之后再严格按照模型编写代码。通过正向建模的方法,可以预先设计实现的架构,使其在设计阶段就尽量符合面向对象的SOLID原则,避免了一上手就写代码而造成高耦合,低内聚的结果;另一方面,很多问题可以在建模时就得到思考和解决,在建模阶段对架构设计进行修改是相对简单的,但是如果已经写代码且已经初具规模,再想修改,就可能考虑不同程度上的重构,就会更加复杂。

​ 但是,对于本单元的作业,如果要利用对规则的理解和“想象”从零开始进行建模,难度会比较大,尤其是此前并没有经历过一次完全意义上的正向建模开发的过程。

​ 因此,我采用了这样一种方法:

  • 首先梳理出要求实现的对象及其功能,以及图书状态转移的条件和结果,此时只是对规则和大致实现的粗糙描述,具体形式只是伪代码(类的实现),状态转移表格;

例如这样的伪代码:

BookShelf

private HashMap<bookId, Integer> books; // manage books in bookshelf

// check if the book whose id is bookId is in the bookshelf
boolean hasBook(bookId) {
    return books.containsKey(bookId);
}

// try to get one book and remove it from bookshelf
boolean getOneAndRemove(bookId) {
    if (hasBook(bookId)) {
        books.get(bookId)--;
        return true;
    } else {
        return false;
    }
} 

// put the book in the bookshelf
void putBookIn(bookId) {
    books.get(bookId)++;
}

以及这样的表格:

借阅流程:

情况判断条件书籍状态转移(立即生效)
无余本在书架一定失败书架----->书架
A类书籍一定失败书架----->书架
B/C类书籍用户持有书籍数量符合借阅条件书架----->用户
B/C类书籍用户持有书籍数量不符合借阅条件书架----->借还处

预约流程:

书类判断条件判断范围状态转移(整理时生效)
A必定失败NULLxx -----> xx
B已经有一本B类书书类xx -----> xx
B没有B类书书类xx -----> 预约处
C有同书号的书书号xx -----> xx
C无同书号的书书号xx ----->预约处
  • 基本梳理完成之后,就用UML进行建模,完成类图、状态转移图、时序图等。此时的UML建模的结果是一个基本的框架,但有很多不完善的细节部分。

  • 完成UML的建模部分之后,开始着手写代码 ,此时遇到对细节处理不确定的问题,先在UML中设计并思考其正确性,结合UML编写代码,通过代码实现的合理性和正确性反馈到UML建模设计中,最终形成相对完善的框架。其中需要处理好这样一个问题,就是在一些场景下,UML建模语言的实现和JAVA语言的实现的难易程度并不一致,因此需要在UML设计和实际代码实现的过程中相互反馈。

本单元作业的架构设计

下面,谈谈本单元我的架构设计。

首先,了解本单元的任务是:

”在一所小型图书馆中,用户借阅图书需要遵守一定的规章制度。我们需要你模拟一个小型的图书管理系统,完成图书馆所支持的相关业务。“

其要求包括:

  1. 管理一些基本单元——如不同序列号的书,书架、借还处、预约处,借阅书籍的用户等。
  2. 实现用户对图书的借阅流程、还书流程、预约流程;图书馆需要完成对书的整理流程。
  3. 一些个性化的实现,例如不同书不同场景的处理流程不一样,用户增加信用积分等等。

结合前一个部分的正向建模的设计与开发的过程,最终实现如下架构:

请添加图片描述

Library

为主体部分,拥有一个书架Bookshelf,一个预约处AppointmentOffice,一个借还处BorrowAndReturnOffice,一个查询机QueryMachine,一个漂流角BookDriftCounter。同时,由于图书馆需要和用户交互,因此需要一个用户列表readers。

BookShelf

书架,提供按序列号查询图书数目的接口,同时具有从书架拿走书和将书放入书架的功能。

AppointmentOffice

预约处,成功预约的书都将放入预约处,同时要给图书馆的整理流程提供接口。

AppointList

预约列表,当有用户发出预约请求时,先将请求加入到请求列表中,在整理的时候,再根据条件判断是否将预约的书加入到预约处。

BorrowAndReturnOffice

借还处,负责和用户及图书馆交互,提供整理流程的接口,在整理流程中和书架图书漂流角进行交互。

BookDriftCorner

图书漂流角,负责管理非正式图书,也要与用户交互。

Reader

用户,管理借书的列表和预约的列表。

BorrowBook

这是我在最后一次作业即hw15中为了记录借书的时间判断是否逾期而设置的类,因为在之前的两次作业中,关于借还书只需要管理一个HashMap建立从BookId到图书数目的映射即可,但是在hw15中,需要有借书的时间的概念,而且只需要在用户借还书的过程中管理,因此,我将图书的借书时间,借书期限与BookId封装成此类,仅被Reader类管理,相比于之前的代码,只需要在Reader类中对获取某一本书的代码中修改即可。

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

其实总体来看,虽然四个单元考察的内容大不相同,但是对面向对象能力的要求是循序渐进的,我的架构设计的思维也得到了很大程度的锻炼,其越发趋于成熟和完美(当然趋于远不足等于)。

UNIT1 从无到有

第一单元要实现的是对一个表达式的化简,考虑的层次结构为表达式-项-因子,而表达式又是因子的一种。第一单元,我刚上手的时候,其实对于递归下降的认知并不是十分清晰,但是在思考不久之后就开始码代码的过程,因而导致了在第一次尝试的过程中,就完全将表达式当作字符串进行字符串层次的处理,这显然是一个非常面向过程的做法,未经过架构设计的结果甚至导致代码严重背离了面向对象的初心。

在第一次尝试失败后,结合实验的代码,我开始对架构进行了新的思考,结合对递归下降的进一步了解,我开始将不同层次建立起不同层次的类,将表达式、项、因子设计成为不同的类,对其增加关于计算和化简的接口,最终实现起来是相对“优美”的。事实也证明,这样的设计,在后面的增加exp因子和导数因子的过程中都起到了一定的积极作用。

从第一次尝试的手忙脚乱,到推倒重来进行架构的设计,第一单元,实现了我在架构设计中的从无到有的一步。

UNIT2 粗糙设计,具体实现

第二单元真可谓是”噩梦缠绕“,多线程,无论是编写代码,还是debug,都显得困难重重,甚至无从下手,因此,如果不提前思考具体实现的架构,那么就极有可能导致自己的线程“不听话”而出现一些莫名其妙的错误。

关于本单元实现的电梯,我对于电梯运行和电梯状态都进行了一定的深入思考,以及查找了相关资料了解策略模式和状态模式的编程思想;与此同时,多线程,离不开锁的使用,但碍于当时对线程调度的极不理解(OS也并未了解到这),所以对锁的实现只是照搬实验的做法。

但好在,我在编写代码之前,就对我要实现的预期目标和架构进行了初步思考,所以写代码的时候并未遇到太大阻碍,整体框架是一次性完成的,只是由于锁的使用的不恰当导致debug花了相当多的时间。

我在这一单元的实现,相比于成熟的架构设计,还有相当大的差距。因为我的设计仅仅停留在对电梯运行的状态、策略、线程交互的思考上,形式上大多是表格、文字,而没有成熟和规范的建模过程。因此,在具体实现中,也遇到了将想法写到代码中却和预期不一致的问题。

UNIT3 JML的限制,局部架构设计

相比于之前的作业,本单元引入了JML语言,JML语言,对要实现的方法都进行了相当严格的限制,我们能够自由发挥的地方只有算法的设计部分。因此,本单元可能并不太能够谈得上架构的设计,更多的可能是为了实现某些算法或采用某些数据结构而进行解耦合的设计。

例如,为了提高对图中判断节点是否连通的效率,我采用了并查集来管理这些节点,于是我设计了对并查集维护、增加边、删边的方法,通过合理的调用关系和顺序的设计,在保证正确性的同时提高程序运行效率。但是这些设计都不能离开JML的规格限制,因此,这些设计是有一定局部性的设计。

当然,其实在这一单元中,我能学到的,还有从JML建模中理解的架构设计的规范,在这一单元的作业中,并未要求我们去编写JML,但是通过读JML,我能感受到这个工具的强大(虽然这项工具在当下已经不太被广泛使用了),因为其为我们的架构设计提供了一种规范化的思路。

UNIT4 UML建模,架构设计趋于完善

这是我们刚刚经历的一个单元,由于其本身对于实现并不做限制,因此,在写代码之前即进行架构的设计是一种合理的方法。我在这一单元中实现的正向建模和开发的过程,已在之前的小节中描述,此处不再赘述。

但要谈及我的感受,那么便是我实实在在地感受到了架构设计的重要性,提前做好规范的架构设计,可以在代码实现的过程中减少很多不必要的麻烦和困难。

四个单元中测试思维的演进

UNIT1 简单黑箱测试和对拍

第一单元的答案正确与否相对容易判断,因此,测试的思路可以相对简单——用黑箱测试形成大量数据点,再利用不同代码进行简单对拍。

UNIT2 多线程的压力测试

对于多线程,针对代码使用瞪眼法的测试方法并不奏效,且多于多线程,仅仅采用代码的单元测试是远远不够的,因为多线程是动态的,交互的,必须考虑其全局运行的状况。

我同样沿用了黑箱测试形成随机大量的数据点,对代码输出逻辑的正确性进行测试。同时,考虑到性能的限制,可以采用压力测试的思路,提供大量边界情况的测试点来验证调度策略的合理性。

UNIT3 Junit的单元测试

本单元的关键词是JML,那么有了JML的限制,相当于已经有了一个全局的架构设计,我们要测试的部分是对JML的实现的正确与否,因此,可以针对不同方法,结合JML语言的前置、后置条件、副作用,进行逐行的测试,通过单元测试验证程序对JML实现的正确性。

UNIT4 多方测试结合

其实思路很简单,由于本单元只对规则做出限制,而不对实现做出限制。因此,一方面,我们需要自己进行单元测试以确保每个模块从设计到代码实现都与预期一致;另一方面,需要通过集成测试来验证整体实现的正确性;当然,还有黑箱测试,投喂大量数据点(当然这一单元考虑交互式评测的特点,需要结合自己的代码实现设计数据点)。

总结和感想

其实,在我看来,这一个学期的学习,更像是一场历练,虽然有上个学期的OOP的基础,但是在上手OO之初,还是遇到了不少困难。尤其是在有限的时间内,学习OO的思想,在实战中锻炼自己的架构设计的思维能力,在经历数不胜数的BUG之后认识到SOLID原则对高内聚、低耦合的重要性,在漫无目的的Debug之后学会用规范的方法进行测试……这些都必须是我们在经历过一次又一次失败和迭代之后才能深刻理解的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值