OO第四次博客作业
架构设计
需求分析
- 类图
- 建立类的单继承;类对接口的多实现;接口的多继承;类和接口各自的关联以及相互的关联。
- 检查类图合法性
- 顺序图
- 建立Lifeline和Endpoint间的消息通信。
- 状态图
- 建立各状态的条件转移。
抽象分层
-
类图
Class
和Interface
继承自同一父类Unit
,而对unit
内含Attribute
和Operation
,而Operation
内含Parameter
-
时序图
UmlInteraction
下有两个同级类UmlLifeLine
和UmlEndpoint
,而UmlMessage
仅用于构建UmlLifeline
和UmlEndpoint
间的消息通信,不需要被包含 -
状态图
FinalState
、Pseudostate
和State
继承自MyState
,去除了Region
层,直接让Region
层的对象从属于StateMachine
。此外UmlTransition
和UmlMessage
类似,被用于建立联系,不需要被包含
构造容器
-
对象存储
本次作业,各个对象都包含ID和名称两个属性,ID与对象一一对应,而名称与对象可能是一对多的关系,而接口要实现的各个函数中被传入的参数是对象的名称,因此需要构造一个容器,其既能够通过名称索引,又能够通过ID索引。
public class DupMap<T> { private Map<String,T> id2Struct; private Map<String, Set<T>> name2Set; public DupMap() { id2Struct = new HashMap<>(); name2Set = new HashMap<>(); } }
因为名称与对象是一对多的关系,故用set存储对象;利用泛型来满足容器适应不同对象的存储。
-
结果存储
因为UML图是静态的,对于传入相同的参数,查询函数只需要在第一次调用时对UML图进行解析,然后将传入参数和结果进行组合并存储。在之后的调用中,只需要返回传入参数对应的结果,而不需要再次解析。
而在我们需要实现的函数中(譬如
getClassAttributeVisibility(String s0, String s1)
),传入的参数为两个,需要构造容器来存储。public class DupElmStruct<T,M> { private T data1; private M data2; public DupElmStruct(T data1, M data2) { this.data1 = data1; this.data2 = data2; } }
UML图
四个单元的架构设计及OO方法理解的演进
表达式求导
学会运用最简单的面向对象分析方法来对给定的需求或问题进行分析,将表达式中的多项式、项、因子抽象成类并进行封装。学会抽象出各个类间的父子关系,理解了面向对象三大特征之一的——继承。体会到采用面向对象思想设计的架构,写出的代码可读性高;由于继承的存在,即使改变需求,那么维护也只是在局部模块,维护起来方便而成本低廉。
电梯调度
学习了三种多线程同步的设计模式——生产者-消费者模式、观察者模式和监听器模式。学习了SOLID设计原则,设计原则是设计模式的基石,虽然很难在整个开发过程中让每个设计都完全满足SOLID设计原则,但是在每个给定的场合,写出遵循本应遵循的设计原则,可以提高代码的鲁棒性、安全性和性能。
电梯的调度参考了磁盘调度算法中的Look算法:
契约式编程
使用JML,我们不得不面临一个问题——在写码之前,必须对类和方法进行很好地抽象、设计。当然这三次作业中,这些是由Client提供的,我们只是作为供应者实现需求。
在这阶段作业中,通过JML和实现接口就已经将顶层的分层和架构制定好了,而对于内部的实现我们可以自由地操作。在这阶段作业对程序有一定的性能要求,这就需要去思考采用何种算法和数据结构。在这阶段作业中,学会了用面向对象去构造容器,这项技能也让自己在UML解析作业的编程中显得更轻松些。
UML解析
架构设计已在第一部分阐述了。这第二次UML解析作业中,因为指导书对很多问题未定义或定义不清,而在后期通过吴老师和助教们在讨论区的解析和说明让这次作业的需求逐渐变得明朗,但是这个过程有点漫长,所以自己在代码的很多细节上改了又改,然而在修改代码的过程中发现,由于分层抽象做得到位,修改类的代码不需要顾虑对其他类有什么负面影响,感觉这是在第三阶段契约式编程中得到锻炼的结果。
在四个单元中对测试的理解与实践的演进
对于第一阶段作业测试多是测试代码中的因对语法不熟悉而导致的错误以及一些不安全的写法,譬如从空的容器取值并操作。对于测试出来的一些不安全写法,可以抛出异常让调用者处理,但修改代码的实现是更适合的做法。这一阶段的测试方法也不太全面,仅仅是根据指导书提出的需求去构造边界数据和若干随机数据,而没有针对实现本身,回过头来看,契约式编程给出后置条件以及前置条件能够在实现模块层面减少不安全的实现。
因为死锁导致的一些结果是不可复现的,所以对于并发程序的测试相较于单线程的难度来说增加了不少。所以对于多线程程序的测试,自己一开始采取对拍来测试,得到有用的测试数据,再借助JProfile工具去观测各个线程的状态从而进一步缩小错误的范围。
对于JML的测试,个人感觉JML的那套工具链不仅跟不上JAVA版本的更新,而且功能还很不完备。这个阶段开始使用JUnit进行单元测试,对结果进行断言来测试方法的实现和模块要求的逻辑是否匹配。
在最后一阶段作业的测试中,主要采用单元测试和黑盒测试的两种方法。
学习OO的收获
软件设计不是黑色艺术,而是有原则可遵循的
再经历了几次重构后,发觉遵守软件设计原则和设计模式来设计和编写的代码无论是在可读性,可维护性以及复用性都有着不一样的效果。在后两个阶段的作业中,自己基本上是在原有代码的基础上按照需求增加代码的功能。
学习了更加丰富的测试手段
写单元测试时能让人更了解需求,并且可以确保当前模块的逻辑,在重构其他地方的时可以信任这个模块。对于工具链更成熟的编程语言,单元测试会让代码验证更快捷。
用随机数据对程序进行黑盒测试,一定程度上考验了程序的鲁棒性和性能。
体会到编写软件的一般流程
从每周五开始对指导书上的需求和要求的功能进行分析,到代码设计,到编写代码,编写期间又反过来对自己的设计进行调整。最后对写完的代码进行测试和验证,在这期间与其他同学交流设计。从十几次OO作业中,体会到了编写软件的一般流程。
窥见面向对象编程的优点
继承和多态是OOP的特性,它增加代码可拓展性的同时,还减少了代码的冗余。
面向对象设计代码相较于面向过程设计代码更利于减少数据的耦合。
对课程的建议
- 建议增加对研讨课展示内容的审核,避免出现一些只包含从几篇博客中摘录内容的演讲
- 建议将第一阶段表达式作业弱化后放到寒假作业中,从而让大家更好的学习java语法而在课程正式开始后好好学习面向对象
- 建议将周五理论课和实验课调开,让学生有充分的准备时间
写在最后
感谢老师们和助教们在OO课程上的付出!老师们和助教们在课下作业和研讨课上的改革带来的良好体验已然感受到了,自己在经历一学期的课程后也有很多收获。