OO_Unit4_UML模型化设计
CSDN博客传送门
@
一、架构设计
(一)第一次作业
本次作业关于UML中的类图。如果说JAVA是用来描述对象及其联系的一种建模语言,那么UML则是用来描述JAVA语言的高层次建模语言,而我们需要完成的作业则是对UML语言的描述及对UML对象的管理。随着层次(此处“层次”并非抽象层次,而是结构层次,高层和底层之间具有组合关系)逐渐地提高,思想的复杂度和抽象性也成倍地提高(需要在多个不同层次去思考问题)。
作业需求分析
- 通过实现
UmlInteraction
这个官方提供的接口,来实现自己的UmlInteraction
解析器。
由于对象之间的关系无非内涵(组合)、外延(继承)、耦合(联系):
- 用缩进表示结构层次(组合关系)
- 用括号
()
表示抽象层次(继承关系) - 用
[]
表示指向对象的指针(联系关系):其中必然联系同时是对象中的组合(需要用缩进表示),偶然联系则直接写在对象后面
用对象图来表示这三种关系如下:
- ClassModel
- Interfaces
- Interface(Object)
- Associations
- Association
- [Object]
- Association
- Generalizations(Generable)
- Generalization1(Generable)
- [Interface]
- Generalization1(Generable)
- Implementations
- Implementation
- [Interface]
- Implementation
- Operations
- Operation
- Associations
- Interface(Object)
- Classes
- Class(Object)
- Associations
- Association
- [Object]
- Association
- Attributes
- Attribute
- Implementations
- Implementation
- [Interface]
- Implementation
- Operations
- Operation
- Parameter
- Operation
- Generalization2(Generable)
- [Class]
- Associations
- Class(Object)
- Interfaces
建立类图
由于JAVA语言对于上述对象图关系的描述非常难受,很多关键词之间发生重复而不得不重名,而且建立这样深层次的结构层次非常困难(从这个角度来说:JAVA很难说是一个优秀的面向对象语言)
粗线表示继承extends,细线表示组合contains,虚线表示联系associates
因为建立了大量的包来管理层次关系,导致用IDEA自带的diagram画出的图不忍直视,怎么调都不能画出期望的那样。一直是一堆点都点不开的包名:
bug修复
几乎所有的bug都出现在对于异常输出上:We expected "No, attribute mData in NetworkParam, mGrad in NetworkParam, are not hidden." but we got "No, attribute mData in Float32NetworkParam, mGrad in Float32NetworkParam, are not hidden." at line 18
在改过基本的问题后,出现了一种特殊的情况,而这种情况恰恰是指导书上几乎没有提示的部分:
在调查C3的Attribute1的Visibility时,确实是重复了,但他的父类已经犯了这个错误,应该先扔出父类的异常。
在这里,检查异常应该有一个明晰的先后关系:而且结构层次越浅,抽象层次越高的对象的异常应该最先被检查出来,抛出之后应该直接拒绝执行下一步。
(二)第二次作业
作业需求分析
本次作业,在上次作业基础上,扩展解析器,使得能够支持对UML顺序图和UML状态图的解析,并能够支持几个基本规则的验证:
对象图在第一次基础上,增加了statechartmodel
collaborationmodel
两个新模型,而且这两个模型几乎和之前的classmodel
没有任何关系,甚至可以看做两个新工程:
- statechartmodel
- StateChartSet
- StateChart
- Region
- Transition
- State
- Region
- StateChart
- StateChartSet
- collaborationmodel
- Collaboration
- Interaction
- Lifeline
- Message
- Interaction
- Collaboration
建立类图
如图所示,建立的类图仅表示类的名称、对象关系,就已经相当复杂:30个类,分散在8个包中。结构层次一目了然,但是由于JAVA语言的局限性,仍然没有按照对象图所示建立类。
checkForUml002实现方式
如类图,checkForUml002
由于只与类有关,交由ClassSet
处理,依次遍历所有类MyClass
,而MyClass
中设有以下函数:对三种情况进行了区分,并依次添加。
public HashSet<? extends AttributeClassInformation> checkForUml002() {
HashSet<AttributeClassInformation> set = new HashSet<>();
//1. 来自重复的<属性,属性>
for (Map.Entry<String, UmlAttribute> entry :attributes.getMap().entrySet()) {
if (entry.getValue() == null) {
set.add(new AttributeClassInformation(entry.getKey(), getName()));
}
}
//2. 来自重复的<属性,对端>
for (UmlAssociationEnd end : getAssociations().getList()) {
if (attributes.contains(end.getName())) {
set.add(new AttributeClassInformation(end.getName(), getName()));
}
}
//3. 来自重复的<对端,对端>,注意null不算
HashSet<String> endSet = new HashSet<>();
for (UmlAssociationEnd end : getAssociations().getList()) {
if (end.getName() != null && endSet.contains(end.getName())) {
set.add(new AttributeClassInformation(end.getName(), getName()));
} else {
endSet.add(end.getName());
}
}
return set;
}
checkForUml008/009实现方式
这两个check是一起完成的,实现方式非常巧妙:在每个MyObject
包含的Generable
中设置了一个HashMap:private HashMap<UmlClassOrInterface, Boolean> map = null;
模型结束后自动将父类加到这个map中去,如果map已经含有这个父类了,将value值置为true
并且用到了深度优先递归,先添加正在查找的对象,如果它没有更新map,则等它更新完毕再继续。最后对于MyObject
中的check
都能一步完成:
public boolean checkForUml008() {
return getGenerable().getMap().containsKey(IdManager.getById(id));
}
public boolean checkForUml009() {
return getGenerable().getMap().containsValue(true);
}
StateChart.getSubsequentStateCount实现方式
相当于找到有向图中某个对象的所有可达点。但是最好是在模型结束时将所有点的可达点都找到。个人用的算法是对每个节点都用一次Bfs,将所有到达的节点都记录在表内。(可能有复杂度更低的算法)
private HashMap<String, HashSet<String>> map;
//<状态的name,后续状态名称集合>,null表示该状态duplicate
//如果是起始和终止态,忽略不计
private void Bfs(String name) {
HashSet<String> arrived = map.get(name);
HashSet<String> nextSet = new HashSet<>(arrived);
while (!nextSet.isEmpty()) {
String fromNode = nextSet.iterator().next();
nextSet.remove(fromNode);
if (map.get(fromNode) == null) {
continue;
}
for (String toNode : map.get(fromNode)) {
if (!arrived.contains(toNode)) {
nextSet.add(toNode);
arrived.add(toNode);
}
}
}
}
二、四个单元中架构设计及OO方法理解的演进
四个单元依次是表达式求导
多线程电梯
JML规格化
UML模型化
,四次的角度各有不同,但视角逐渐变得全面,变得有层次、有深度。尤其是最后一次竟然直接以JAVA语言本身为对象进行语言上的思考与设计。
OO对我的最大影响并不是一个个具体的算法(尤其是图论中的Bfs,Floyd,Dijstra,Dfs);不是一些具体的数据结构(尤其是容器类的使用HashMap,HashSet,Arraylist,LinkList);不是一些具体的多线程交互模式(如生产者——消费者模式、工厂模式、订阅——发布者模式);不是一些具体的设计模式(如单例模式、守护者模式、观察者模式);不是一些具体的设计原则(尤其是SOLID原则:SRP,OCP,LSP,ISP,DIP);却是面向对象在哲学视角的思想与方法论。
此篇博文详细讨论了面向对象的哲学视角与方法论。
三、四个单元中测试理解与实践的演进
测试依次接触到:自动化测试、规格化测试、单元测试
自动化测试:用代码将数据管理好,并自动去运行整个程序,将数据导入并进行对比的方式。
规格化测试:针对方法而言,对每个方法都独立地写一个LLR(Low Level Request,即规格),让相应软件自动生成数据进行验证。
单元测试:针对方法而言,是一种内部的样例,直接对类进行初始化,调用相应函数,检验结果。
应用异常代价:软件都是为了某一个应用而实现的,在软件工程真正运行过程中大量的从用户端传来的数据可能会导致工程出现漏洞,有时甚至会出现极大的经济上的损失。这样的测试也是有代价的,更多时候要权衡测试的成本和应用异常代价。
个人认为,写一个单元测试时非常费力的,写了半天只检验了一个函数,而且还不能保证100%正确;写一个LLR(规格化测试)也是非常费力的,而且从LLR到实现也是吃力不讨好的过程,不如直接用自然语言描述的需求更好实现。虽然有所谓的代码覆盖率、分支覆盖率层次上的检验,其时间和空间代价对于一般的应用程序都是不可接受的。只有航天航空那种要求非常严格的软件工程才会有这样严苛的要求。
最后剩下的就是自动化测试了,可以说这个是检查代码功能正确性,已经代码效率最佳的方法。首先将所有测试样例放在同一个文件目录下,可以用python语言import OS
,用命令行调用自动生成JAVA可运行程序,依次再运行所有样例,与结果进行比较。这样的自动化测试就相当于评测机了。说到底,这个才是最为实用的测试方式。
四、三个具体改进建议
- 对优秀代码进行全面而有层次地解读,学习大佬的思想
- 与其他学科进行交叉解读,如面向对象在哲学视角的思考,在计算机网络层面的关联
- 作业更加贴合实际一些、具体一些,如写一些关于UI方面的,写游戏这样能提起同学兴趣,并对同学有些实际的作用