目录java
第一次做业比较简单,仅包含类图的解析。正确理解UML元素的含义,以及每种UMLElement
的各个属性的所指向的东西,就能比较容易地完成这次做业。算法
这里我构建了两个类:ClassRelations
以及InterfaceRelations
用来存储类和接口所包含的属性、方法等类图的基本信息,同时还要保存父类(父接口)、实现的接口,用来递归查找。是一种比较容易想到的实现方式。编程
类图以下:设计模式
为了加快对ClassName
的查找和判断,使用了一个邻接表来存储同名的类,方便查找相应的类。安全
private HashMap<String, ArrayList<UmlClass>> classNameMap = new HashMap<>(); private void checkClassName(String className) throws ClassNotFoundException, ClassDuplicatedException { if (!classNameMap.containsKey(className)) { throw new ClassNotFoundException(className); } else if (classNameMap.get(className).size() > 1) { throw new ClassDuplicatedException(className); } }
第二次做业在第一次做业的基础上,增长了对UML状态图和UML时序图的解析。此外,还增长了三个模型有效性检查的规则,分别是:不含重名的成员、不能循环继承、不能重复实现接口。bash
与第一次做业相比,总体的架构变化不大,总的思想仍然是把每一种UML元素放到对应的UML图中,根据需求对这些数据加以维护。总体稍加改动后直接继承上次的做业。多线程
本次实现的MyUmlGeneralInteraction
直接继承了上次的MyUmlInteraction
。对于状态图和时序图,与上次做业相似的,我建立了两个类StateMachineRelations
和SequenceRelations
用来保存与状态图和时序图相关的信息。架构
类图以下:并发
对于模型有效性的检验编程语言
R001:针对下面给定的模型元素容器,不能含有重名的成员(UML002)
与检测类名重复相似的,在ClassRelations
类中,添加属性和关联对端的时候,对同名的属性和关联对端进行计数,就能够得到找到重名的成员。
R002:不能有循环继承(UML008)
R003:任何一个类或接口不能重复继承另一个接口(UML009)
这两个规则都是和继承或接口的实现相关的,因此放在一块儿处理。继承/实现关系构成了一个有向图,循环继承和屡次实现接口的优先图,具备以下特色:
因此,将边权定义为优先图节点之间的路径数目,那么也就能同时判断是否存在循环继承和重复继承
对于节点i
到节点j
的路径数目,对于中间节点k
,存在以下的关系:route(i, j) = route(i, j) + route(i, k) * route(k, j)
(貌似是动态规划的状态转移?有没有算法大佬能够讲一下,没学过不是很懂。顺便期待下学期算法有一个好的收获)
使用UmlClassOrInterface
接口对类和接口的关系统一建模,具体的代码以下:
private HashMap<UmlClassOrInterface, HashMap<UmlClassOrInterface, Integer>> extensionGraph = new HashMap<>(); //包含继承关系和接口实现关系 public static <T> void floyd(HashMap<T, HashMap<T, Integer>> graph) { for (T k : graph.keySet()) { for (T i : graph.keySet()) { if (i.equals(k) || !graph.get(i).containsKey(k)) { continue; } int ik = graph.get(i).get(k); for (T j : graph.keySet()) { if (k.equals(j) || !graph.get(k).containsKey(j)) { continue; } int kj = graph.get(k).get(j); if (!graph.get(i).containsKey(j)) { // 更新路径数 graph.get(i).put(j, ik * kj); } else { int newCnt = graph.get(i).get(j) + ik * kj; graph.get(i).put(j, newCnt); } } } } } protected Set<UmlClassOrInterface> check008() { Set<UmlClassOrInterface> set = new HashSet<>(); for (UmlClassOrInterface i : extensionGraph.keySet()) { if (extensionGraph.get(i).containsKey(i)) { set.add(i); } } return set; } protected Set<UmlClassOrInterface> check009() { Set<UmlClassOrInterface> set = new HashSet<>(); for (UmlClassOrInterface i : extensionGraph.keySet()) { for (UmlClassOrInterface j : extensionGraph.get(i).keySet()) { if (extensionGraph.get(i).get(j) > 1) { set.add(i); } } } return set; }
架构设计是面向对象这门课的重中之重。因为咱们的课程是按照单元推荐,每一个单元的每一次做业都是在前一次的基础上,进行增量任务,因此一个好的架构可让后续的做业更加容易完成。
第一单元:多项式求导
前两次的做业由于需求还比较简单,还算是有一个比较看得过去的架构。把多项式分解为单项式,再把单项式分解成幂函数和三角函数,一个多项式的求导问题就被分而治之。就是下降耦合,抽象出各个不一样的类,将它们独立出来,再把每一个对象组合在一块儿来统一处理。
可是随着第三次做业的发布,这个架构设计的弊端就显现出来了。扩展性较差,致使第三次做业加上嵌套求导以后,几乎推倒重写。仍是说明前两次做业对面向对象机制的理解不够深入,以及对Java语言掌握不够熟练(不会使用接口泛型等特性)
第二单元:多线程电梯
多线程电梯调度主要就是要理解清楚生产者-消费者模型以及发布-订阅模型这些课上讲到的重要的多线程编程的设计模型。多线程最重要的问题就是线程的同步与互斥、线程间通讯的问题。
第二单元多线程电梯调度主要运用的就是“生产者-消费者模型”,构建一个两级的关系,经过共享队列在线程间传递信息。输入线程与调度器线程经过共享队列交互,调度器线程再与电梯线程经过共享队列交互。这个架构设计延续了三次做业,主要的变化都是调度器内部和电梯内部的调度算法。
这个单元,课上老师也介绍了一些设计模式,好比单例模式、工厂模式等等。虽然实际写代码的时候没有特别注意使用这些设计模式,可是这些学习设计模式的思想,也是在知道咱们怎么去设计类,怎么去设计接口,使得程序具备良好的扩展性和鲁棒性。固然,最重要的仍是理解了多线程编程的方式和要点。
第三单元:JML规格设计
Java Modeling Language 是用于Java语言建模的语言,是一种契约化的设计思想。这个单元主要是围绕着图论展开的,因为有JML规格做为提示,总体比较顺畅。做业难度依次递增,从路径到地铁图层层递进。将不一样的需求(最短路径,最少票价,最少换乘)分别用不一样类的建模完成,下降耦合。因为都是图,因此把通用的图的算法单独提出来放到一个类,做为静态方法来调用(其实这样是有点危险的,一旦这里出错,全部地方都出错了,呜呜呜我就错在这了)
JML为程序的开发设计提供了一个统一的规范规格,虽然估计JML实际应用不是不少,JML描述的规格,对方法、类等程序单元进行了严格的约束,这些正确详实的规格,相较于天然语言,能更加规范地描述需求,减小歧义,保证开发的速度与质量。这种”先设计规格再实现“的约定对于大型工程的协同开发有不少的好处。
第四单元:UML图解析器
这一单元主要的任务是解析UML图中的各类元素。理清楚UML各个元素的各个字段的意义以及之间的关系之后,推动的就比较顺利。
根据UML图的组织形式,能够很容易地联想到仿照UML图地组织形式来构建每一个类,按照类图、顺序图、状态图分别构建模型和存储相应数据。把实现的各部分划分红不一样的责任单元,创建各个类来分别负责完成各自的任务。与第三单元有些相似。
第一单元:多项式求导
学习了讨论区编写测试脚本的方法,我采用以下的方式来发现别人的Bug:
testData.txt
文件中Git bash
执行sympy
用来计算标准答案,以及互测屋全部输出的答案log.txt
文件中,人工比对使用到的工具包括:Python Sympy, Git bash, VS code等。
第一单元因为没有官方包的介入,输入输出都是本身处理,因此WRONG FORMAT是测试的重中之重。
第二单元:多线程电梯
多线程编程比较特殊,因为多线程并行具备不肯定性,且不一样的调度算法会形成不一样的输出结果,因此不存在惟一的正确答案,评测机实现起来也有点复杂。因此我自测阶段,没有进行大量数据的测试。
互测阶段,主要是读代码,看有没有出现一下几种问题:
第三单元:JML规格设计
第三单元使用了很重要的一个测试工具:JUnit。
单元测试是一个强有力的测试工具。相比大量数据的黑盒测试,JUnit单元测试能够在更快速的找到代码漏洞,花费更少的时间,达到很好的验证正确性的效果。JUnit还有一个有点是有测试覆盖率的指标,这是随机数据的测试所不能比拟的。
除了使用JUnit进行测试,还构造了一个比较强的随机数据生成器来进行测试,与同窗的输出进行比较。(仍是晚了,发现Bug的时候已经截至了)
第四单元:UML图解析器
第四单元用StarUML花了几个比较特殊的图,好比重复继承、循环继承相关,以及带多的环的状态图等特殊状况,来测试代码的正确性,并重点测试了几个用到了深度优先搜索算法的指令。期末考试也比较忙,测试频次不是不少。
测试先行是我印象最深的。
不管是什么工程,写代码、连电路、焊板子这些,最重要的都是进行全面的测试。测试必定要和编写代码同步进行,或者先于写代码完成。第三单元最后一次做业惨痛的教训让我记住了“测试先行”这个道理,不要等到最后再匆匆忙忙测试而后提交,一个隐蔽的错误形成的可能就是满盘皆输。
一个学期的OO课程终于结束啦。就像登山同样,如今到了山顶终于能够喘口气了。每周的做业走在催逼着本身不断前进,虽然一个学期基本没过过一个舒服完整的周末,但回过头来看,这一万多行代码,每一个单元博客的总结记录,看获得这一路上的进步和收获。虽然有作的不尽人意的地方,但仍是蛮有成就感的。
至少最后表彰总结课上没有空手回去😁
开个测试分享区,能够分享测试机或者测试数据
第二个是但愿互测能有些改变,这个我在第一单元的博客中也写到过。
这三次的互测后,我相对如今的互测制度提一点小小的建议:
- 仍然划分A, B, C三档,可是分组把这三组混合编组,例如8人间能够{2A, 3B, 3C},7人间{2A, 3B, 2C}
- 找到某个等级做业的Bug得对应等级得分,而与本身的做业等级无关
- 这样作得好处是:每一个人均可以看到不一样水平的代码,给C组和B组的同窗更多学习的机会;避免了高段位“大眼瞪小眼”的尴尬,还有低段位“菜鸡互啄”的无趣;找到高段位的Bug更有成就感和分数奖励
- 缺点是:规则较为复杂,实现起来可能比较麻烦,并且不必定每一个人都接受这种制度
有同窗说:“通过这三次互测,个人bash脚本和Python 写的比原来好多了。”
固然,这些都只是我我的的想法,抛砖引玉,还请助教组学长学姐和老师们能研究出更好的制度,回归互测的本质。
或者简单一些,把互测的人数减小到5至6人可能会合适些,这样能够多读代码,以避免一些同窗互测阶段直接放弃。
研讨课参与程度不过高,感受“研讨”的氛围不是很明显。
感受第三单元能够提到第一单元来。特别是多项式求导做为第一单元,并且最后一次做业,确实有点困难,不如先从JML开始,逐步熟悉Java语言和面向对象的建模方式。