一、架构设计分析
第一次作业:
在MyUmlInteraction中实现了以下HashMap和List来存储应该得到的树状结构和输入处理过程中应该暂时存储的信息
private HashMapeleMap; private HashMap classMap; private HashMap classna; private HashMap numofclass; private HashMap interMap; private HashMap interna; private HashMap numofinter; private HashMap ass; private HashMap end; private List atList; private List opList; private List geList; private List paList; private List irList;
可以看到在HashMap的键值对的value中,是我自己定义的新的类,在每一个新的类中都有一个相应的HashMap来存储应该是他的子节点的信息,例如在一个类中可以包含有属性Attribute,操作Operation,继承Generalization和接口实现InterfaceRealization,所以在MyUmlClass中有如下实现:
private HashMapopList; private HashMap name2att; private ArrayList attList; private HashMap numofsame; private UmlGeneralization generalization; private ArrayList interReal;
其他的自定义的类也按照这样的思路进行了实现。
对于查询类数量,操作数量和属性数量等操作,由于在输入处理中已经将相应的信息存为一个树状结构,对于这类简单信息的查询只需找到相应的储存结构,进行相应的返回即可。特殊的是操作的查询可能设计到类型,那么只需将该类中的操作遍历一遍,返回相应类型的操作数量,同时将是否遍历过的标志置为1,以后再查询该类中其他类型的操作时则可以直接返回相应数值。
public int getClassOperationCount(OperationQueryType type) { if (!hasCount) { operationCount(); } if (type == OperationQueryType.NON_PARAM) { return nonparaOp; } else if (type == OperationQueryType.PARAM) { return hasparaOp; } else if (type == OperationQueryType.RETURN) { return retOp; } else if (type == OperationQueryType.NON_RETURN) { return nonretOp; } else { return opList.size(); } }
对于属性数目和关联数目的查询,由于还要计算父类的相应数目,所以当该类的generalization不为null时,即递归调用其父类的相应的查询方法。以查询属性的方法为例如下:
public int getClassAttributeCount(AttributeQueryType type, HashMapclassMap) { if (type == AttributeQueryType.SELF_ONLY) { return attList.size(); } else { if (attriCount == -1) { if (generalization != null) { attriCount = attList.size() + classMap. get(generalization.getTarget()). getClassAttributeCount(type, classMap); } else { attriCount = attList.size(); } } return attriCount; } }
对于作业其他的方法实现大致相同,都是通过给定的相应名称,找到相应的存储结构进行遍历或者是递归遍历。
此外,为了正确实现规格定义的两个异常,对于有需要异常检查的变量都有三个HashMap来存储相应的,并且他们的键值分别为<id,对应的自定义类>,<name,对应的自定义类>,<name,名字为name的自定义类的数量>。因此对于NotFoundException,如果<name,对应的自定义类>中不包含键name即异常,对于DuplicatedException如果对应的<name,名字为name的自定义类的数量>中的值大于1,则异常。但由于一个自定义类可以通过他的name和id找到,在使用的时候容易发生混淆,所以造成了第一次作业的bug
第一次作业bug分析:
在getImplementInterfaceList()函数中由于不仅要递归调用该类的父类的相应方法,对于他实现的接口的父类也要调用相应的方法,但是由于我在接口中实现的getImplementInterfaceList()中使用的是<name,对应的自定义类>进行的一系列操作,但是最后返回的是name而不是id,所以在最后的输出结果中,可能会少一些接口。当将接口中也改为使用<id,对应的自定义类>进行操作,并最后返回id时,bug修复。
二、四个单元中架构设计及OO方法理解的演进
第一单元
第一次单元作业是实现一系列的求导操作,由于当时刚接触Java,刚接触面向对象,对于作业的编写还是很大程度上停留在面向过程的层面上,习惯于按照输入处理--过程求导--结果化简输出的模式来写,虽然在之后的2次作业中,有试着对于每一个因子,项,表达式建立相应的类来分开管理,但是感觉还是没有领会到面向对象的精髓。
第二单元
第二单元的作业是电梯调度的系列作业,也是我第一次接触到多线程的编程。在这次作业中不仅学习到了生产者-消费者模式,了解并运用了线程安全的锁与wait-notify机制的相关知识,并且在程序的实现上,输入请求的处理、电梯运行和请求的调度分别实现在不同的类中,他们通过互相共享相应的资源以及锁机制来安全的实现相应的功能。对于每个类我们只需知道它需要什么,输出什么,对于其内部的实现则不需要关心,通过这一次作业我也第一次感觉自己算是稍微入门了一下面向对象程序的编写。
第三单元
第三单元是根据给定的JML实现一个Path的管理功能,在课程组给我们提供的输入输出接口的帮助下,极大的减小了我们的作业负担,并且由于规格的限定,很好的明确了我们的编程目标,也让我体会到了规格以及正确的规格编写在编程开发中的重要作用。在第三单元作业的编写中,我们只需在相应的方法中按照规格实现相应的代码,同时通过学习给定的规格,架构设计也让我进一步学习了面向对象的思想。
第四单元
第四单元的作业是对UML图的各种解析,也是我认为最体现面向对象思想的一个单元作业,为了能够实现作业给定的操作要求,首先要实现对一个UML图的树状储存结构的存储,而树中的每一个节点就是一个对象,我们只需要知道建立一个节点需要什么,它能提供什么信息,而相应方法操作的实现我们只需要在各自的类内部实现并向外提供一个接口即可,用户并不需要了解方法的具体实现。但是,我的实现还是存在缺陷的,因为一次运行只能对一个UML图进行解析,我想在之后的改进中可以针对UML图建立一个类,这样就可以对自定义数目的UML图进行解析。
三、测试理解与实践演进
前两次作业的测试,我都是自己手动构建测试集来进行测试,所以测试覆盖的范围小,测试的效率低,对于一个bug可能要花很长的时间去测试,其中构建的很多测试数据可能在类型上就是重复的,是浪费时间的测试,在第三次单元作业中,我才开始尝试使用自动化的测试工具Junit,但是由于不太熟练,在其基础上还是辅以手动构建的测试数据进行辅助测试。在之后的编程中,我一定会多多使用自动化的测试方法,争取早日掌握。
四、课程收获
经过一个学期的学习,首先,我从一个对Java这门语言什么都不懂的菜鸟,变成了一个可以写一写比较复杂的java程序的入门级新手,其次多线程和规格、UML图等课程的设计也让我对java的工程开发有了更深入的了解。其次最重要的是通过课程的学习,我初步掌握了面向对象的思想,这是我认为在计算机学习中最重要的东西,只有当一种思想深入我的心中,成为一种自己的潜意识,当遇到问题时不自觉地使用面向对象的思想去思考解决它,在一个学期的训练下,我开始学会试着用面向对象的思想去解决问题,剩下的就是不断的练习巩固,让其成为自己的一种习惯。
五、课程改进建议
1、可能是这个学期比较巧合,放假使实验课调了很多次,但是有时候也会出现实验课的内容和理论课的内容相差的比较多,实验课的内容考察的可能是理论课讲过两三周的内容,希望课程组可以调整一下理论课和实验课的进度,尽量使其协调.
2、第一单元的作业,大部分精力都花在处理WF的问题上,感觉这不应该是面向对象课程的重点,可以尝试减少在格式上的要求是学生更集中在面向对象的架构设计上。
3、对于自动化测试工具希望可以设计相应的课时进行讲解帮助同学们进行运用和掌握。