面向对象设计与构造第四单元和课程总结

曾华旭-22371554 学生 2024-06-15 11:17:55

面向对象设计与构造第四单元和课程总结

Ⅰ 第四单元总结

一、建模与开发

  • 1.建模与开发的总体过程。在第一次作业中,我认真地先用StarUML基本设计好整体结构再开始编写代码,体会了整个过程,设计了相对完善的结构;但后两次作业感觉StarUML画画改改太麻烦了偷懒进行了一些设计就直接写代码,之后再补充UML图。不过经过第一次的设计,我还是体会到了建模与开发的整体过程。其实使用建模工具先设计再编码往往也不能全部完美设计好开始编码,在实际实际操作中可能是先进行UML建模,中途发现存在一定问题,对UML整体的结构进行了比较大的修改,(这里也体现了先建模的好处——如果直接写代码可能就不得不重构,而在比较轻量的建模操作下重构的代价就小得多)基本完成框架以后就可以编码,在接触到代码的细节后可能还会遇到一些没有建模时尚未考虑周全的细节问题,比如某一个操作需要更多的参数支持等,这时候再返回对建模进行一些修改。建模和编码是互动的过程。
  • 2.良好的建模的优势。首次作业比较良好的设计给后来带来了一些方便之处。比如第一次作业的整体架构泛用性比较好,控制(分发)器-组件的大框架在后两次作业没有更改,已经实现的类中的方法只有少量的修改,贯彻了开闭原则。再比如,开始时设计的Books类也是在构建UML模型时设计出来的,让大量组件能直接调用,实现比较简单;后续再添加和一个漂流角也可以方便地持有和复用。

二、架构设计

  • 第一次作业

    • 整体设计 根据要求,构造了书架、借还处、预约处和客户四个位置,同时将预约信息也抽象为一个类Order。并由一个控制器LibraryController统一进行命令的分发和控制,这个控制器尽可能只进行对不同位置的交互,不进行具体的操作。控制器持有书架、预约处和借还处,并分别用哈希表和链表存储客户和预约信息;由于书架、借还处、预约处为图书馆组成部分,故LibraryController与其为聚合关系,而客户(同学)及预约信息与它为组合关系。
    • 图书信息设计 考虑到各个对象都持有一系列书籍,并且往往对书籍进行一些增删改查的操作,因此在设计的时候考虑加入了Books这个类,让图书持有者(或位置)将Books作为属性持有,将对书籍的一系列操作封装到这个类里面,让操作更简便,代码复用性更高。
    • 功能划分设计 将图书馆相应的功能放在图书馆的位置,例如取书放在预约处,借书、还书放在借还处;借阅权限由于涉及到当前借阅情况,因此直接放在客户处。

      img

  • 第二次作业

    • 第二次作业的整体设计沿用了第一次作业的结构,仍包括书架、借还处、预约处、客户和预约信息以及控制类和书籍类。
    • 新增的类 新增漂流角类实现图书漂流功能。漂流角通过实现和借还处相似的添加书籍和归还书籍操作,完成图书漂流;图书的借阅次数信息直接存储在借阅处,并在借还处新增图书分类功能的函数,以便直接高效地支持图书的升级功能,图书升级以后就直接加入书架中的正式图书,无需进一步操作。另外新增一个管理图书节约期限的类,使用了单例模式,只需按如下配置即可,能够方便地支持新增和修改。
        private DateManager() {
            terms.put(LibraryBookId.Type.B, 30);
            terms.put(LibraryBookId.Type.C, 60);
            terms.put(LibraryBookId.Type.BU, 7);
        terms.put(LibraryBookId.Type.CU, 14);
        }
      

      img

    • 书籍状态变化 根据书籍的位置划分状态。当图书初始化后加入书架,被捐赠后加入漂流处;书架中的书(B/C)被借阅失败则被放入借还处,而被借阅成功则加入客户处;漂流角的书同理;在开关整理中若处于预约清单则放入借阅处;在借阅处被取走则加入客户处;客户还书则加入借还处;闭关整理时借还处的书放入书架;开关整理时若预约处的书逾期则放入书架。

      img

  • 第三次作业

    • 信用分维护 直接在用户内添加信用分,并在相应命令实现处添加信用分维护即可。由于信用分的维护位置比较分散,难以集中实现,因此我没有新增一个信用分管理的类,避免比较奇怪的关联关系。这里有一个需要明显调整的地方,在之前的捐献实现中,并没有记录捐献的用户,因此本次作业中书籍升级导致的用户信用分提升需要另外实现。我的设计是给控制类新增捐献数据记录的辅助类。
    • 预约权限调整 由于预约规则变化,在用户中新增了用户预约权限的查询方法,并在其中维护了一些支持性的数据比如当前正在生效的各类预约数量。

      img

  • 追踪关系

    • 通过先设计后编码再调整的方式,UML的模型和代码内容实现了一致性。比如类图与程序能互相找到名字相同的类,类图中每个类的属性和方法一致,类图中的继承、实现、关联关系应和代码实现中的关系保持一致。状态图的逻辑关系和代码中实现的关系一致,trigger的方法能在代码中找到对应等。

Ⅱ 课程回顾

一、设计思维

  • 逐步调整的设计,这是在本课程中对设计的最大体会。刚开始设计的时候可能遇到设计考虑因素过多导致过分封装和抽象或者设计太少导致后续代码可能重构的问题。一个良好的折衷是先对基本框架进行设计,并预留好进行扩展的可能性,然后再在编码的过程中逐步调整。这在第一单元和第四单元中都充分体现。
  • 多线程和共享对象的设计,在多线程单元的学习中重点掌握了利用共享对象进行通信的方法,这种方法不但对多线程程序适用,实际上对一般的程序也有一定的启发意义,即在设计时,如果遇到多个类共同关联的一个数据包,也可以打包成共享对象进行;对于全局数据可以采用单例模式进行实现。
  • 规格设计,在第三单元中了解到了基于规格的设计方法,这种方法尽管非常麻烦,但是无二义性,完成规格设计以后编码变得比较容易,同时从交流的角度来说是对程序的功能的严格描述。
  • 模型设计,如果说规格是对设计的粒度足够细的说明的话,模型则是对设计的在各种侧重点上足够整体的抽象,比如说类图是对类属性方法和关系的抽象,顺序图是类间交互的抽象。这种抽象方法有利于可视化和从整体上看清系统的设计,从而便于看出设计潜在的问题和进行设计的演示。

二、测试思维

在前三单元中我都用自行构造的程序进行了一定的测试,其中第二单元进行了比较完备的测试,在测试、互测和debug中积累了一定的测试经验。所有代码的验证都采用的是数据生成-答案验证的测试方法,比较遗憾没有进一步了解形式化验证方法。

  • 1.数据生成 经过一个学期的自主测试和被测试的环节,可以看出足够强的数据是保证测试效果的关键要素。测试数据一般包括大量随机数据、边界数据、不同条件下的数据。在进行测试的时候,我开始容易陷入一个误区,即生成超大量的数据进行验证,如使用上千条数据测试电梯或几十万条数据测试关系网,但是结果往往证明这样的测试既耗费时间往往又效果不佳。原因在于生成的数据同质性较强,虽然数据量大但是覆盖范围非常有限。后来进行了一定的改进,可能的思路包括实现系统性的随机性而非某一条命令的参数随机,比如提高社交网络的命令分布随机性;还有就是在设计阶段就一步步分析出潜在的问题,有针对性的进行测试,比如针对多项式嵌套时的形参/实参字母一样的情况进行测试。不过目前这个问题还没有很好地解决。
  • 2.对拍/状态检查 “答案验证”一步的方法,对拍实现简单但只适用于程序结果十分固定的情形,在实际工程问题中也难以使用;而状态检查方法值得重点掌握,只是有的时候对状态检查问题的复杂度和源程序实现的复杂度一致,这就使得测试难以展开。因此需要进行权衡。在第一个单元内,检验输入的多项式和输出的多项式是否相等是难以简单实现的,如果要准确验证,相当于再写一个多项式化简器,这显然不现实,如果引用第三方库带入数据计算,则难以处理大量指数很大的情况,因此采用对拍的方式是比较方便高效的。从电梯单元开始,状态检查就是比较合理的方式了,当然第三单元也可以考虑对拍。状态检查需要维护系统当前的状态,然后针对每条输入检验合理性。经过本学期的测试,我了解了按照状态进行测试的流程,以电梯单元为例,当电梯开门时,就需要检查当前电梯是否处于运行状态、时间是否距离关门超过相应时间、跨越楼层是否为1、楼层是否在相应范围、是否同另一部电梯相撞,经过一系列检查合法后再修改系统进入下一状态。
  • 3.基于JML的测试 如果在设计之初就构建好每个方法的模板,测试也会变得更加容易,基于规格的测试体现了这一点。对于比较简单的情况,只需要将规格进行翻译就可以实现方法的检测,对于复杂的情况还需要进一步构造程序,不过本质上仍然是对规格的描述。比如下面求最短路径的规格,我们不可能完全忠实地按照规格穷尽两个点之间的所有路径(因为可能有环,则路径有无数条),因此进行一定程度的设计,比如用标记的方法排除环来实现测试。利用规格进行测试对于我来说是新的思路,而且实际工作中也有利于编写代码和编写测试的并行。
        @ ensures  (\exists Person[] pathM;
        @          pathM.length >= 2 &&
        @          pathM[0].equals(getPerson(id1)) &&
        @          pathM[pathM.length - 1].equals(getPerson(id2)) &&
        @          (\forall int i; 1 <= i && i < pathM.length; pathM[i - 1].isLinked(pathM[i]));
        @          (\forall Person[] path;
        @          path.length >= 2 &&
        @          path[0].equals(getPerson(id1)) &&
        @          path[path.length - 1].equals(getPerson(id2)) &&
        @          (\forall int i; 1 <= i && i < path.length; path[i - 1].isLinked(path[i]));
        @          (\sum int i; 0 <= i && i < path.length; 1) >=
        @          (\sum int i; 0 <= i && i < pathM.length; 1)) &&
        @          \result==(\sum int i; 1 <= i && i < pathM.length - 1; 1));
    
  • 4.单元测试与集成测试 通过本学期的学习也体会到积极进行单元测试有利于在编码阶段就发现问题,减少后续debug带来的麻烦;不过有些时候单元测试也难以发现深层次的问题,比如说多项式化简的函数替换过程涉及的深拷贝问题、递归下将层次结构带来的递归找不到终点问题需要进行继承测试,通过打断点找到存在问题的位置。综合利用两种方法能更高效测试。
  • 5.交互式评测 最后一个单元我没有写测试代码,当然也更没有进行交互式评测。不过这种方法似乎能比较有效地帮助测试覆盖更多的情形,将来可以进一步探索。

三、课程收获

  • 在本学期的学习中深入了解了面向对象的特性,更了解了不局限于面向对象的代码设计方法和设计模式,比如递归相关的层次化设计及相关的递归控制和克隆问题、支持代码复用的继承/接口设计、使用生产者/消费者模式和共享对象进行的多线程同步互斥方法、死锁的避免、形式化方法、模型的抽象等。
  • 提高了代码能力。本学期OO课程应该给我们带来了几千行的代码训练量,在IDE的使用、git的各种操作中提高了编写代码和工具使用的熟练度。
  • 熟悉了一些测试方法,可以从前一个部分看出测试思维的演进。
  • 最后祝愿OO课程越办越好
...全文
58 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

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

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