本学期的最后一次OO blog啦~
第一周的内容主要是类的查询命令,由于这三周的内容都是迭代开发的(且三周的内容在上一周的内容上近乎是完全隔离的,只是可能会因为前两周的实现导致第三周实现的简易度),因此这里就以第三周的架构来讲解
(个人感觉Association关系的描述有点不太直接,这周博客没类图的要求,因此就直接摆Diagram,其实想摆官方的豪华版Diagram,无奈导出的太大,插件不支持了,悲)
总的来看三周的内容的分配还算比较平均,第一周的任务是实现类图的查询指令,第一周的麻烦在于对第四单元整个工程项目和任务的理解(个人开始做的时候着实是一头雾水,看了半天代码才知道开发的部分),另外就是第一周各个类的架构的关联度还是挺大的吧(相比于二三周的内容),整体的实现的关联度很大,可能会因为一个实现不合理而改架构,还有会因为找到一个bug而被迫改架构,总之第一周的实现个人感觉挺痛苦的,第二周相对较好,内容是实现顺序图和状态图的查询指令,第二周麻烦点在于状态图割点的查找和顺序图题目的理解,相较之下不是很难,第三周的内容是UML的规则检查,也是很多指令都是非常简单的,麻烦点在于循环继承的判断和重复继承的判断,顺便复习了一下Tarjan算法,感觉熟悉Tarjan算法的话这一周非常简单,或者干脆不用算法优化,暴力dfs也是非常简单的
用了MyClass, MyInterface, MyMethod三个交互类 + MyImplementation, Main, Debug三个运行类(Parser为第三周为了不丢CodeStyle分被迫把MyImplementation分开写的,可以看成一个)实现
输入指令格式:CLASS_COUNT
举例:CLASS_COUNT
输出:
Total class count is x.
x
为模型中类的总数。指令1的实现只需要在MyImplementation里加一个class的容器来记录,执行该指令的时候返回size就可以
输入指令格式:CLASS_SUBCLASS_COUNT classname
举例:CLASS_SUBCLASS_COUNT Elevator
输出:
Ok, subclass count of class "classname" is x.
x
为直接继承类 classname
的子类数量;Failed, class "classname" not found.
classname
的类时,输出上述内容;Failed, duplicated class "classname".
classname
的类时,输出上述内容。这一操作构建的执行构建在myClass类内,异常判断在执行指令段,由于以下还会会多次出现需要判断classname的异常,这里给个我的写法(比较无脑)
private final HashMap<String, MyClass> classMap; public int getClassSubClassCount(String className) throws ClassNotFoundException, ClassDuplicatedException { int cnt = 0; Iterator<Map.Entry<String, MyClass>> iterator = classMap.entrySet().iterator(); MyClass myClass = null; while (iterator.hasNext()) { Map.Entry<String, MyClass> entry = iterator.next(); if (entry.getValue().getName().equals(className)) { myClass = entry.getValue(); cnt++; } } if (cnt == 0) { throw new ClassNotFoundException(className); } else if (cnt > 1) { throw new ClassDuplicatedException(className); } return myClass.getClassSubClassCount(); }
对于myClass内的getClassSubClassCount()方法,是在构造myClass时记录其加入的subClass,然后返回那一容器的size
输入指令格式:CLASS_OPERATION_COUNT classname
举例:CLASS_OPERATION_COUNT Elevator
输出:
Ok, operation count of class "classname" is x.
x
为类 classname
中的操作个数;Failed, class "classname" not found.
classname
的类时,输出上述内容;Failed, duplicated class "classname".
classname
的类时,输出上述内容。说明:
这一指令的实现同指令2
输入指令格式:CLASS_OPERATION_VISIBILITY classname methodname
举例:CLASS_OPERATION_VISIBILITY Taxi setStatus
输出:
Ok, operation visibility of method "methodname" in class "classname" is public: www, protected: xxx, private: yyy, package-private: zzz.
www
/xxx
/yyy
/zzz
分别表示类 classname
中,名为 methodname
且实际可见性分别为 public
/protected
/private
/package-private
的操作个数;methodname
的操作,则 www
/xxx
/yyy
/zzz
全部设置为 0;Failed, class "classname" not found.
classname
的类时,输出上述内容;Failed, duplicated class "classname".
classname
的类时,输出上述内容。说明:
methodname
的操作的可见性信息。这一指令的实现仅需要遍历MyClass内的method容器,然后开一个Map记录次数即可
输入指令格式:CLASS_OPERATION_COUPLING_DEGREE classname methodname
举例:CLASS_OPERATION_COUPLING_DEGREE Taxi setStatus
输出:
Ok, method "methodname" in class "classname" has coupling degree: coupling_degree_1, coupling_degree_2, coupling_degree_3.
methodname
的操作共有 3 个,它们的操作的耦合度分别为 coupling_degree_1
、coupling_degree_2
、coupling_degree_3
,且这些操作中不存在重复操作;methodname
的操作,则传出一个空列表;Failed, class "classname" not found.
classname
的类时,输出上述内容;Failed, duplicated class "classname".
classname
的类时,输出上述内容;Failed, wrong type of parameters or return value in method "methodname" of class "classname".
classname
中所有名为 methodname
的操作存在错误类型时,输出上述内容;Failed, duplicated method "methodname" in class "classname".
classname
中所有名为 methodname
的操作存在重复操作时,输出上述内容。说明:
这一指令的实现相较较为麻烦,最开始看的时候主要是理解任务说明的统计方式吧,然后就又是Map/Set集合的Compare了(然后我开始时竟然又一次因为重写equals出了bug)
我的实现方式是在MyClass中进行Method的重复判断和name的重复判断
private final String name; private final HashMap<String, MyMethod> methods = new HashMap<>(); public List<Integer> getClassOperationCouplingDegree(String methodName) throws MethodWrongTypeException, MethodDuplicatedException { ArrayList<MyMethod> operations = new ArrayList<>(); for (Map.Entry<String, MyMethod> entry : methods.entrySet()) { if (entry.getValue().getName().equals(methodName)) { operations.add(entry.getValue()); } } boolean duplicatedFlag = false; for (int i = 0; i < operations.size() - 1; i++) { for (int j = i + 1; j < operations.size(); j++) { HashMap<NameableType, Integer> parameterList1 = new HashMap<>(); HashMap<NameableType, Integer> parameterList2 = new HashMap<>(); for (UmlParameter umlParameter : operations.get(i).getParameters().values()) { parameterList1.merge(umlParameter.getType(), 1, Integer::sum); } for (UmlParameter umlParameter : operations.get(j).getParameters().values()) { parameterList2.merge(umlParameter.getType(), 1, Integer::sum); } if (parameterList1.equals(parameterList2)) { duplicatedFlag = true; } } } ArrayList<Integer> returnList = new ArrayList<>(); for (MyMethod method : operations) { returnList.add(method.getCouplingDegree(this)); } if (duplicatedFlag) { throw new MethodDuplicatedException(this.name, methodName); } return returnList; }
然后再MyMethod下进行统计
private final String name; private UmlParameter returnParameter; private final Map<String, UmlParameter> parameters = new HashMap<>(); public int getCouplingDegree(MyClass myClass) throws MethodWrongTypeException { ArrayList<String> hasAdd = new ArrayList<>(); int cnt = 0; if (returnParameter != null && returnParameter.getType() instanceof NamedType) { if (!type.contains(((NamedType) returnParameter.getType()).getName()) && !((NamedType) returnParameter.getType()).getName().equals("void")) { throw new MethodWrongTypeException(myClass.getName(), this.name); } } for (Map.Entry<String, UmlParameter> entry : parameters.entrySet()) { if (entry.getValue().getType() instanceof NamedType) { if (!type.contains(((NamedType)entry.getValue().getType()).getName())) { throw new MethodWrongTypeException(myClass.getName(), this.name); } } } for (Map.Entry<String, UmlParameter> entry : parameters.entrySet()) { if (entry.getValue().getType() instanceof ReferenceType) { if (!((ReferenceType)entry.getValue().getType()). getReferenceId().equals(myClass.getId())) { if (!hasAdd.contains(((ReferenceType)entry.getValue().getType()). getReferenceId())) { cnt++; hasAdd.add(((ReferenceType)entry.getValue().getType()).getReferenceId()); } } } } if (returnParameter != null && returnParameter.getType() instanceof ReferenceType) { if (!((ReferenceType)returnParameter.getType()).getReferenceId(). equals(myClass.getId())) { if (!hasAdd.contains(((ReferenceType)returnParameter.getType()).getReferenceId())) { cnt++; hasAdd.add(((ReferenceType)returnParameter.getType()).getReferenceId()); } } } return cnt; }
输入指令格式:CLASS_ATTR_COUPLING_DEGREE classname
举例:CLASS_ATTR_COUPLING_DEGREE Taxi
输出:
Ok, attributes in class "classname" has coupling degree x.
x
为类 classname
的属性的耦合度Failed, class "classname" not found.
classname
的类时,输出上述内容;Failed, duplicated class "classname".
classname
的类时,输出上述内容。说明:
这一操作相对较易,只需要开一个集合统计每个Attribute的ReferenceType情况即可
输入指令格式:CLASS_IMPLEMENT_INTERFACE_LIST classname
举例:CLASS_IMPLEMENT_INTERFACE_LIST Taxi
输出:
Ok, implement interfaces of class "classname" are (A, B, C).
classname
实现了 A
、B
、C
这 3 个接口;classname
没有实现任何接口,则传出一个空列表;Failed, class "classname" not found.
classname
的类时,输出上述内容;Failed, duplicated class "classname".
classname
的类时,输出上述内容。这一操作也仅需要在类内添加好其实现的接口即可,需要注意的是要先设置好接口的继承关系,然后再完成实现比较方便(不直接也行,只不过感觉相对会很麻烦),这一操作的关键其实也是接口上的递归继承关系和多继承关系
输入指令格式:CLASS_DEPTH_OF_INHERITANCE classname
举例:CLASS_DEPTH_OF_INHERITANCE AdvancedTaxi
输出:
Ok, depth of inheritance of class "classname" is x.
x
为类 classname
的继承深度;Failed, class "classname" not found.
classname
的类时,输出上述内容;Failed, duplicated class "classname".
classname
的类时,输出上述内容。这一操作我的实现方式是每次处理继承时都从根类开始设置深度标志,也是一个简单的递归
新增了MyInteraction, MyCollaboration, MyLifeLine实现,其中MyCollaboration在本周任务中并非必要,因为顺序图中的Represent的加入关系链,我添加这一个类相对比较易于实现这一要求(但实现后发现不是必需的,只不过阴差阳错刚好是下一周的一个指令,只能说是赚大了)
我的主要操作在Interaction中实现
(感觉对着这个比较方便阐述~)
输入指令格式:PTCP_OBJ_COUNT umlinteraction_name
举例:PTCP_OBJ_COUNT normal
输出:
Ok, participant count of umlinteraction "umlinteraction_name" is x.
x
为顺序图模型 umlinteraction_name
(UMLInteraction)中的参与对象(UMLLifeline)个数;Failed, umlinteraction "umlinteraction_name" not found.
umlinteraction_name
的顺序图模型时,输出上述内容;Failed, duplicated umlinteraction "umlinteraction_name".
umlinteraction_name
的顺序图模型时,输出上述内容。这里异常的判断同上周,调用直接return这里的lifeline的size
输入指令格式:PTCP_CREATOR umlinteraction_name lifeline_name
举例:PTCP_CREATOR normal door
输出:
Ok, lifeline "lifeline_name" in umlinteraction "umlinteraction_name" can be created by "creator_name".
creator_name
为顺序图模型 umlinteraction_name
中能创建 lifeline_name
的参与对象;Failed, umlinteraction "umlinteraction_name" not found.
umlinteraction_name
的顺序图模型时,输出上述内容;Failed, duplicated umlinteraction "umlinteraction_name".
umlinteraction_name
的顺序图模型时,输出上述内容。Failed, lifeline "lifeline_name" in umlinteraction "umlinteraction_name" not found.
umlinteraction_name
中不存在名为 lifeline_name
的参与对象时,输出上述内容;Failed, duplicated lifeline "lifeline_name" in umlinteraction "umlinteraction_name".
umlinteraction_name
中存在多个名为 lifeline_name
的参与对象时,输出上述内容;Failed, lifeline "lifeline_name" in umlinteraction "umlinteraction_name" is never created.
umlinteraction_name
中的参与对象 lifeline_name
没有收到创建消息时,输出上述内容;Failed, lifeline "lifeline_name" in umlinteraction "umlinteraction_name" is created repeatedly.
umlinteraction_name
中的参与对象 lifeline_name
收到多条创建消息时,输出上述内容。说明:
这里只需要特判一下CreateMessage然后直接根据Create Message去Lifeline里找就可以了
输入指令格式:PTCP_LOST_AND_FOUND umlinteraction_name lifeline_name
举例:PTCP_LOST_AND_FOUND normal door
输出:
Ok, incoming found message and outgoing lost message count of lifeline "lifeline_name" of umlinteraction "umlinteraction_name" is x and y.
x
为顺序图模型 umlinteraction_name
(UMLInteraction)中 lifeline_name
收到的 Found 的消息个数,y
为发送的 Lost 消息个数;Failed, umlinteraction "umlinteraction_name" not found.
umlinteraction_name
的顺序图模型时,输出上述内容;Failed, duplicated umlinteraction "umlinteraction_name".
umlinteraction_name
的顺序图模型时,输出上述内容。Failed, lifeline "lifeline_name" in umlinteraction "umlinteraction_name" not found.
umlinteraction_name
中不存在名为 lifeline_name
的参与对象时,输出上述内容;Failed, duplicated lifeline "lifeline_name" in umlinteraction "umlinteraction_name".
umlinteraction_name
中存在多个名为 lifeline_name
的参与对象时,输出上述内容;说明:
这里也是在加Message的时候特判一下来源或者是目标中包含Endpoint的情况然后对应去Lifeline中添加即可
这里新增了MyRegion, MyTransition, MyState, MyStateMachine(我的MyStateMachine类可有可无,直接用UMLStateMachine就可以的)
主要操作还是在MyRegion中实现(其实同顺序图一样,是顺序图包含Interaction,状态图包含Region,但是这两个都可以是一对多的关系,不像类图中的关系,因此这样通过Region和Interaction的方式集中实现更方便)
输入指令格式:STATE_COUNT statemachine_name
举例:STATE_COUNT complex_sm
输出:
Ok,state count of statemachine "statemachine_name" is x.
x
为状态机模型 statemachine_name
(UMLStateMachine)中的状态个数;Failed, statemachine "statemachine_name" not found.
statemachine_name
的状态机模型时,输出上述内容;Failed, duplicated statemachine "statemachine_name".
statemachine_name
的状态机模型时,输出上述内容。说明:
由于有仅有一个Initial State,这里直接return state的size + finalState的size + 1即可,异常处理同前面一样
输入指令格式:STATE_IS_CRITICAL_POINT statemachine_name statename
举例:STATE_IS_CRITICAL_POINT complex_sm open
输出:
Ok, state "statename" in statemachine "statemachine_name" is a critical point.
statemachine_name
中的 statename
状态是关键状态时,输出上述内容;Ok, state "statename" in statemachine "statemachine_name" is not a critical point.
statemachine_name
中的 statename
状态不是关键状态时,输出上述内容;Failed, statemachine "statemachine_name" not found.
statemachine_name
的状态机模型时,输出上述内容;Failed, duplicated statemachine "statemachine_name".
statemachine_name
的状态机模型时,输出上述内容;Failed, state "statename" in statemachine "statemachine_name" not found.
statemachine_name
中不存在名为 statename
的状态时,输出上述内容;Failed, duplicated state "statename" in statemachine "statemachine_name".
statemachine_name
中存在多个名为 statename
的状态时,输出上述内容。这里就是求割点了,我的做法是通过MyState类,在添加State转移的时候在State的nextState里面添加一个state作为后续bfs的遍历范围,然后删除前后做一个bfs,求遍历到的state的集合,进行两次集合的比对
这里就拿一下MyRegion里的方法吧
private final String parentName; private MyState initialState; private final HashMap<String, MyState> states = new HashMap<>(); private final HashMap<String, MyState> finalStates = new HashMap<>(); public boolean getStateIsCriticalPoint(String stateName) throws StateNotFoundException, StateDuplicatedException { if (judgeStateByName(stateName) == 0) { throw new StateNotFoundException(this.parentName, stateName); } else if (judgeStateByName(stateName) > 1) { throw new StateDuplicatedException(this.parentName, stateName); } if (finalStates.size() == 0) { return false; } HashSet<String> originalArrive = new HashSet<>(); originalArrive.addAll(states.keySet()); originalArrive.addAll(finalStates.keySet()); Queue<MyState> bfsQueue = new LinkedList<>(); bfsQueue.add(initialState); while (!bfsQueue.isEmpty()) { MyState peek = bfsQueue.poll(); HashMap<String, MyState> nextStates = peek.getNextStates(); for (String id : nextStates.keySet()) { if (originalArrive.contains(id)) { bfsQueue.add(nextStates.get(id)); originalArrive.remove(id); } } } boolean flag = false; for (String finalStateId : finalStates.keySet()) { if (!originalArrive.contains(finalStateId)) { Debug.print(finalStateId); flag = true; break; } } if (!flag) { return false; } HashSet<String> deleteArrive = new HashSet<>(); deleteArrive.addAll(states.keySet()); deleteArrive.addAll(finalStates.keySet()); bfsQueue.add(initialState); while (!bfsQueue.isEmpty()) { MyState peek = bfsQueue.poll(); HashMap<String, MyState> nextStates = peek.getNextStates(); for (String id : nextStates.keySet()) { if (deleteArrive.contains(id)) { if (nextStates.get(id).getType() == 1 || !nextStates.get(id).getName().equals(stateName)) { bfsQueue.add(nextStates.get(id)); deleteArrive.remove(id); } } } } Debug.print(deleteArrive.toString()); for (String finalStateId : finalStates.keySet()) { if (!deleteArrive.contains(finalStateId)) { return false; } } return true; }
但是割点还是有更优的dfs方法的,只是我的架构下不是非常易于操作(操作起来感觉不像面向对象了),因此就暴力bfs了
输入指令格式:TRANSITION_TRIGGER statemachine_name statename1 statename2
举例:TRANSITION_TRIGGER door_sm open close
输出:
Ok,triggers of transition from state "statename1" to state "statename2" in statemachine "statemachine_name" are (A, B, C).
statemachine_name
从 statename1
迁移到 statename2
的事件共有 3 个,分别为 A
、B
、C
;Failed, statemachine "statemachine_name" not found.
statemachine_name
的状态机模型时,输出上述内容;Failed, duplicated statemachine "statemachine_name".
statemachine_name
的状态机模型时,输出上述内容;Failed, state "statename1" in statemachine "statemachine_name" not found.
statemachine_name
中不存在名为 statename1
的状态时,输出上述内容;Failed, duplicated state "statename1" in statemachine "statemachine_name".
statemachine_name
中存在多个名为 statename1
的状态时,输出上述内容;Failed, transition from state "statename1" to state "statename2" in statemachine "statemachine_name" not found.
statemachine_name
中未找到任何从状态 statename1
到状态 statename2
的迁移时,输出上述内容。说明:
null
,或仅包含空白字符(空格和制表符 \t
)。这里我的实现方式是自建一个MyTransition类,在这个类中添加一个events集合进行记录,在这种方式下这个指令较好实现,就不废话了
第三周的任务是做模型的有效性检查,这一部分会在实例化完毕后自动按序触发执行,不需要通过指令的形式执行。执行中一旦发现不符合规则的情况,将直接退出,不进行后续有效性检查和指令查询。
或许这一周的任务最能体现第四单元的特色吧,这一单元的内容的细节非常多,需要整天的研究指导书 + 问助教,只要忽略了一点,或许实现起来并不难,但肯定会对应一个bug,这一周的内容更能体现这一点(就是那种不难,但是感觉一直在研究指导书 + 修bug)
对于这一周的测试,额外提的一点就是,建议单独设立异常检测模块,与构造过程分开,保证好异常出现的顺序,不然测试时同时出现多个异常(官方只有一个异常)的时候,可能几个人都是对的,但是报的顺序不一样也对不上拍
这一周整体的架构上没做什么改动(最大的架构改动是因为CodeStyle把上周卡了500行上限的MyImplementation拆出来了一个Parser)
规则解释:
目前所有类图的元素中,除了以下元素之外,其余元素的 name
字段均不能为空:
direction
为 return
的 UMLParameter输出:
Failed when check R001, a certain element doesn't have name.
这一个指令在建好图后实现非常容易,但是容易忽略的点在可能会有无ParentID的类图元素(官方的测试数据中肯定是不会出现的,是我们线下自己测实现这一功能比较方便),还有就是接口中的属性和方法也需要检查,但是其他地方的实现并不需要这些属性和方法,因此加到个人实现的MyInterface的意义不大,我个人建议的实现就是直接在MyImplementation里面建一个容器存储这些类图元素,然后遍历一下除了顺序图的Attribute,逐个判断一下是最直接的
规则解释:
输出:
Failed when check R002, "member" in "Container", "member2" in "AnotherContainer" has duplicated name.
Container
类中的 member
,与 AnotherContainer
类中的 member2
。说明:
name
字段都为空,则规定这两个成员不是重名。这里主要是对于这一功能的理解吧,Association在类图中相当于一个变量的引用,这些应当与原有类图中的Attribute的name不重复,从写程序的角度理解好这个问题,实现起这一规则还是挺容易的
规则解释:
该规则只考虑类的继承关系以及接口之间的继承关系。所谓循环继承,就是按照继承关系形成了环。
例如下面的场景:
interface A extends B { // something here } interface B extends A { // something here }
这里就构成了一组最简单的循环继承。
输出:
Failed when check R003, class/interface (A, B, C, D) have circular inheritance.
说明:
这里我选择的是Tarjan算法进行判断,来找size大于1的SCC,由于以前写的比较多,这里基本上是用面向过程的方式写了个Tarjan(x
(注:这里由于自测的原因,class的多继承不易保证,且保证后的测试强度会差很多,因此在我们的数据实现中没有保证官方的多继承上的要求(其实是最开始做作业的时候没看到这周仍满足这一条件,这一规则检查的内容写的挺迷惑的(),当时由于太晚助教老师也没回,就按可以保证写的,不过测试效果倒是不错))
private int cnt1; private int dfn1; private int top1; private int interfaceAmount = 0; private final int[] low1 = new int[arraySize]; private final int[] num1 = new int[arraySize]; private final int[] sccno1 = new int[arraySize]; private final int[] stack1 = new int[arraySize]; private final ArrayList<ArrayList<Integer>> graph1 = new ArrayList<>(); private void dfs1(int u) { stack1[top1++] = u; low1[u] = num1[u] = ++dfn1; for (int i = 0; i < graph1.get(u).size(); ++i) { int v = graph1.get(u).get(i); if (num1[v] == 0) { dfs1(v); low1[u] = Math.min(low1[v], low1[u]); } else if (sccno1[v] == 0) { low1[u] = Math.min(low1[u], num1[v]); } } if (low1[u] == num1[u]) { cnt1++; while (true) { int v = stack1[--top1]; sccno1[v] = cnt1; if (u == v) { break; } } } } private void tarjan() { // interface cnt1 = top1 = dfn1 = 0; for (int i = 1; i <= interfaceAmount; i++) { if (num1[i] == 0) { dfs1(i); } } // class cnt2 = top2 = dfn2 = 0; for (int i = 1; i <= classAmount; i++) { if (num2[i] == 0) { dfs2(i); } } }
Tarjan算法的好处就是可以在一次dfs后便找到所有的scc,对于强连通的判断效率非常高
规则解释:
该规则考虑类之间的继承关系、接口之间的继承关系,包括直接继承或间接继承。
例如下面的场景
interface A { // something here } interface B extends A { // something here } interface C extends A, B { // something here }
接口 C 就重复继承了接口 A(一次直接继承,一次通过接口 B 间接继承)。
输出:
Failed when check R004, class/interface (A, B, C, D) have duplicated generalizations.
说明:
重复继承的判断,其实指导书中的叙述地不是那么地全面,实际上得从官方的数据中去猜,可以说是官方指导书的配套数据说的倒很有意义,且后来同学问后助教的回答也很准(总之指导书上确实不准 tao),实际上说的更准确的表述或者说实现方式,应当是,在类图的关系全部建好的基础上,把某一点作为起始点,只要图中其他任意一点满足,起始点到该点存在多条路径,那么起始点就是一个发生重复继承的点
可以观察到的就是,如果一个点已经是发生重复继承的点了,那么这一点的所有子类的点也是发生重复继承的点(利用这一关系可以更好地剪枝)
这里就列一下我的dfs方法吧
private boolean rule004Flag = false; private final Set<MyInterface> rule004Set = new HashSet<>(); private final ArrayList<ArrayList<Integer>> graph = new ArrayList<>(); private final boolean[][] hasArrived = new boolean[arraySize][arraySize]; private final boolean[] isMultiTracesPoint = new boolean[arraySize]; private void dfs(int u, int c) { if (isMultiTracesPoint[u]) { isMultiTracesPoint[c] = true; rule004Set.add(interfaces.get(num2Id1.get(c))); rule004Flag = true; return; } for (int i = 0; i < graph.get(u).size(); ++i) { int v = graph.get(u).get(i); if (!hasArrived[c][v]) { hasArrived[c][v] = true; dfs(v, c); } else { isMultiTracesPoint[c] = true; rule004Set.add(interfaces.get(num2Id1.get(c))); rule004Flag = true; return; } } } private void multiTraces() { for (int i = 1; i <= interfaceAmount; i++) { dfs(i, i); } }
重复继承这里其实是可以跟上面的tarjan一块用一个dfs的,我本来也是合在一块写的(不要问为啥后来又分开了,问就是CodeStyle)
规则解释:
public
。输出:
Failed when check R005, all attributes and operations of interface must be public.
说明:
这一指令就是名正言顺的好实现 + 没坑,直接检查parentId在接口中,就检查这一属性的可见性即可
规则解释:
represent
属性必须指向在同一 Sequence Diagram 下定义的 Attribute
。输出:
Failed when check R006, each lifeline must represent an attribute.
这一操作我已经在第二周的任务中实现了,主要方式就是在MyCollaboration中加一个Represent的容器,然后先放入所有的Represent和Interaction,然后加Lifeline的时候向这一Interaction所在的Collaboration里查即可
规则解释:
Lifeline
是一个实例对象,在被销毁(即收到 Delete 消息)后再收到任何消息都是不合理的。输出:
Failed when check R007, a lifeline receives messages after receiving a delete message.
说明:
这里仅需要在原来的addMessage处加入一个对DeleteMessage的特判,并在LifeLine设置状态记录是否Delete即可
规则解释:
输出:
Failed when check R008, a final vertices has at least one outgoing transition.
这里既可以去State的连接情况处检查,也可以在加入Transition时检查Source,实现起来也很简单,就不多说了
规则解释:
输出:
Failed when check R009, a state has ambiguous outgoing transition.
说明:
name
字段字符串相同”。这一规则检查实现起来不是很困难,但是要理解指导书中的守护条件的判定方式,其实仅有一个Trigger时,Guard可以为空,但是有两个(或多个)时,按照指导书中的说法应该理解成必须两个(全部)都不为空,且均不相同,换句话说,一个为空,一个为非空,这种情况也是不允许的,理解后实现起来还是不难的
这一单元的测试个人做的还算比较充分,虽然前两周还是在ddl前一天才开始写,但是这两周突然发现了肖哥哥跟我基本同步,两个人边写边讨论写的还是挺快的,基本晚7-早3就差不多写完了,到早6完成评测姬,早8初步测试,起来后再问问一文,再进一步测试一下,感觉时间还是挺充裕的,这一单元的强测也都是满分,还是挺开心的
不过也算是真的感受到了评测姬的意义,毕竟数据构造与程序的书写还是两个方向的思路,作为验证是完全没有问题的,这三周基本都是以为没bug,写完评测姬发现还有一堆bug(第一周2个大bug,接口继承和操作耦合度,第二周5个小bug,第三周是指导书理解的问题出现的bug,数据 + 程序一起修),以及在这里再次感谢岳哥哥和一文哥哥的帮助,and 肖哥哥的陪伴,第四单元总的还是非常满意的,而且很轻松hhh
现在看来,OO整体这四个单元的进步还是很明显的,从预习单元开始,那时在抽象层的多态、向上向下转型以及接口等还是小心翼翼的,第一单元重写java方法、深克隆时感觉发现了新大陆,还有那会儿的迭代器 + remove的使用,都让我感觉心力憔悴,第二单元的时候,OO的架构已经非常了解了,那会儿最痛苦的就是造轮子的bug实在不好de,最后还是换用了look策略先完成作业,深刻体会到了\(架构≠性能/效率\)的道理,第三单元的时候,OO的架构就已经非常习惯啦,而且也没什么可以设计架构的地方,到了第四单元,类间的耦合度已经有非常完全的层次啦,而且第四单元的内容设计也是非常的体贴,一个单元比一个单元顺利。总的来看,OO架构设计是一个单元比一个单元优秀了,也算是从重构走上了迭代开发的正道吧(x,现在回头看预习时凌乱的代码已经非常滴羞愧了hhh
在OO的四个单元的开发下,12次的编程作业,我个人书写了10次的评测机代码,另外两次是借用了一文同学的,以及记不清多少次借用了岳哥和一文gg的数据,也自己在他们的数据上迭代过了好几版(但有两版第三单元的和一版第四单元的,仅迭代了数据过滤),在此先再次感谢ywgg和ygg的无私帮助,个人在这12次的测试中深刻体会到了“测试”的意义,很多次(特别是第四单元)都是在评测姬搭出来后才发现了原设计中的bug,也是这样才是真正体会到了架构涵盖测试设计上的优势吧,特别是测试段也是由自己完成的时候,除了第二单元第一周的迷茫,后面便意识到这一点的重要性,非常确定的bug位置对于修复来说太重要了,测试去耦对于OO的设计来说也太重要了,and 高效的测试对于OO的互测环节也实在太重要了,快乐OO,从测试开始~
如果是理解与实践过程的话,我觉得三四单元的内容对于这一点的体现更加明显,三单元的JML上的理解,以及防范严格按照JML写导致的qgvs超时问题(x,四单元的内容理解 + 任务方向 + 数据范围,到官方数据的理解,感觉都是一个由需求决定设计的过程,如果需求理解出现偏差了,很大可能实践起来设计出来的代码在后来发现理解上出问题后便全部废掉,总之就是下手不能太早,即使ddl已在眼前(x,想清楚再动手
OO结束啦~,yysy,自从第二单元造轮子的尝试下,感觉OO项目从小到大全都经历个遍了(,体会最大的主要有以下三个点吧
多么痛的领悟(x,除此之外感觉自己还发现了自己的问题就是不像那些综合性能很高的dl们和那些不灭金身的同学们那样始终如一的坚持,自己虽然好像很多次强测都很高,但是还有两次的摆烂估计整体成绩还会非常低,以及甚至有几次都是写完就开始放松,晚上才开始搭评测机,结果自己就轻松测出来自己的一堆bug,毫无疑问互测能Hack很多人,但是自己也寄的很惨(,9次作业进了2次B房1次C房(C房那次是第三单元评测机出问题,没检测出来没有一个异常输出没换行,报那个异常的就都烂了。。强测寄了一半),甚至自己都感觉自己的评测机不像别人那样是为了更好地完成自己的作业而生的,反而像是为了Hack而写的(x,我个人互测交数据还是比较保守的,但最后还是Hack了很多人,好像一共中了87刀,得益于我评测机写得慢导致的强测“优秀”发挥,感觉自己实在是应当从那种纠结要不要写评测机的心理中走出来,不是等作业截止了才纠结完写个评测机,实际上也是12次作业都搞了评测机。。。,或许自己该果断一点儿吧,不能总拖拖拉拉的,and 希望下一个学期就能改了这些毛病吧
OO结束了,真心感谢OO课程组提供这么好的课程,每周的OO感觉充实了整个学期的生活,甚至很多时候没有OO或者放假OO延期都会感到很空虚,有时甚至感觉面向对象对于我们这种面向不了对象的人是一件非常好滴替代奥,OO总让我感到我现在的面向对象水平还不足以让我面向对象,and 希望以后可以继续为面向对象而面向对象~
最后再次感谢ywgg和ygg在本学期对我的无私帮助,嘿嘿(*^▽^*)
第一单元对于第二周到第三周的过渡实在太小,应该适当扩充一些实验内容,至少可以添加一些在实现上较易在优化上较难的内容,让同学们有更多的发挥机会,还有重写java内置函数的想法 and 深克隆感觉可以在课程中讲一讲?感觉同学们的讨论和在第一单元中应用的意义非常大,特别同学们刚开始写java程序的时候,感觉挺有必要补充一下的
第二单元对于生产者消费者模型的考察无可厚非,但不应当连续多年如此偏袒LOOK + 自由竞争策略的性能,或者说,个人感觉不应当在如今课程继承状态如此好的当下,出一道仅用LOOK + 自由竞争策略的思想便贯穿整个多线程的最优性能测试的题目,个人尝试了Master-Worker策略,感觉二者在本学年课程多线程题目下的实现复杂度实在过大,而且当讨论区 + 学长往期经验 + 同学对于额外开发和优化的需求,几乎所有人都使用了LOOK + 自由竞争策略完成电梯的作业,相信课程组也非常了解电梯单元如果不加调度算法下的仅LOOK + 自由竞争实现的难度有多么小,个人感觉对于学生们在多线程的认识上不利
第三单元的内容相较之下过于少且易,感觉可以紧缩一下,比如两周完成?或者是丰富一下内容,原本两周的内容感觉一周完成都很轻松,或许三周的内容一周内完成也不是不可能,然后再扩充一下社交网络的其他内容?比如博客作业中提到的售卖等,对社交网络的模拟进一步扩充一下,不然以当前的第三单元JML中的内容量实在难以和其他几个单元的任务量相匹配