OO 第四单元总结
架构设计
第四单元作业是对UML图进行解析,并支持若干种对图中元素的查询和模型有效性检查。
public class MyUmlClass extends MyUmlElement implements MyUmlClassOrInterface {
private final UmlClass umlClass;
private final Visibility visibility;
private MyUmlClass parent;
private final ArrayList<MyUmlClass> subClasses;
private final ArrayList<MyUmlInterface> interfaces;
private final ArrayList<MyUmlAttribute> attributes;
private final HashMap<String, ArrayList<MyUmlOperation>> operations;
private int operationCount;
private final ArrayList<MyUmlAssociationEnd> endsOnTheOtherSide;
...
}
现在的问题是如何将这些信息添加到MyUmlClass这样的类中。由于UML图中的元素是通过parentId、referenceId等方式表现其中的关系,所以需要保存id与元素类之间的关系。另一个问题是UML图元素传入的顺序不能保证,有可能存在解析一个元素时,其parentId对应的元素还没有出现的情况。因此解析元素的过程中,需要进行两次遍历。这两次遍历均在一个单独的类MyUmlParser类中进行,解析后再传入MyImplementation类中。
第一次遍历由newElements方法完成,不解析元素,只对每个UmlElement建立相应的MyUmlElement类,并存入一个以id为键的HashMap中。由于第三次作业中R007需要考虑元素传入的顺序,另外声明了一个名为umlElementsInOrder的ArrayList记录元素传入的顺序。
private final HashMap<String, MyUmlElement> umlElements;
private void newElements(UmlElement... elements) {
for (UmlElement element: elements) {
MyUmlElement myUmlElement = null;
if (element instanceof UmlClass) {
myUmlElement = new MyUmlClass((UmlClass) element);
} else if (element instanceof UmlInterface) {
myUmlElement = new MyUmlInterface((UmlInterface) element);
} else if (element instanceof UmlAttribute) {
myUmlElement = new MyUmlAttribute((UmlAttribute) element);
} else if (element instanceof UmlOperation) {
myUmlElement = new MyUmlOperation((UmlOperation) element);
} else if (element instanceof UmlParameter) {
myUmlElement = new MyUmlParameter((UmlParameter) element);
}
...
if (myUmlElement != null) {
umlElements.put(element.getId(), myUmlElement);
umlElementsInOrder.add(myUmlElement);
}
}
}
第二次遍历由parseElements完成。按照umlElementsInOrder的顺序遍历所有元素,通过parentId等将各元素联系起来,并存储一些需要查询的元素。对每种元素均建立了一个parse*()方法,统一由parseElements()调用。
private void parseElements() {
for (MyUmlElement element: umlElementsInOrder) {
if (element instanceof MyUmlClass) {
parseClass((MyUmlClass) element);
} else if (element instanceof MyUmlInterface) {
parseInterface((MyUmlInterface) element);
} else if (element instanceof MyUmlAttribute) {
parseAttribute((MyUmlAttribute) element);
} else if (element instanceof MyUmlOperation) {
parseOperation((MyUmlOperation) element);
} else if (element instanceof MyUmlParameter) {
parseParameter((MyUmlParameter) element);
}
...
}
}
例如,parseClass()方法将MyUmlClass存入classes中。classes是一个以类名为键,以存储了所有同名类的ArrayList为值的HashMap(需要按名查询的元素均按这种方式存储),并检查R001以及更新classCount。
private void parseClass(MyUmlClass umlClass) {
if (isEmptyName(umlClass.getName())) {
emptyNameFlag = true;
}
ArrayList<MyUmlClass> sameNameClasses =
classes.computeIfAbsent(umlClass.getName(), k -> new ArrayList<>());
sameNameClasses.add(umlClass);
classCount++;
}
当需要通过parentId连接元素间的关系时,以在类中添加操作为例:
首先在MyUmlClass类中增加一个addOperation方法,然后在parseOpernation方法中在umlElements中按umlOperation的parentId找到其所属的MyUmlClass,并调用MyUmlClass的addOperation方法。其他元素的关系处理也都大同小异。按这种方法就可以将UML图中的元素联系起来。
private void parseOperation(MyUmlOperation umlOperation) {
if (isEmptyName(umlOperation.getName())) {
emptyNameFlag = true;
}
MyUmlElement umlElement = umlElements.get(umlOperation.getParentId());
if (umlElement instanceof MyUmlClass) {
((MyUmlClass) umlElement).addOperation(umlOperation);
}
}
MyImplementation类的构造方法中,构造一个MyUmlParser并将UML元素传入,MyUmlParser负责解析元素和处理部分便于在此时处理的有效性检查(如R007),然后将解析后的信息传入MyImplementation。MyImplementation类中直接存储的只有所有的UmlClass、UmlInterface、UmlStateMachine、UmlInteraction,其他UML元素存储在上述四种元素对应的类中。查询UML图中元素和部分不在MyUMlParser中的有效性检查由MyImplementation调用其中存储的元素中的相关方法完成。
架构设计思维及OO方法理解的演进
第一单元中还没有面向对象的思想,几乎完全是照着试验代码模仿的,不能体现出对面向方法的理解。第一单元的代码只能说是可以完成任务,但编写时没有太多关于架构思考。
第二单元的重点在于多线程,大体上的架构主要是套用流水线模式、生产者消费者等模型。但由于太过在意架构上的一些问题,影响了电梯调度方式,导致电梯性能不佳。
第三单元中,由于有官方包的限制,大体上的架构已经确定,没有太多可以自由发挥之处。
第四单元中,编写代码前首先进行了很多思考,得到了一个自己比较满意的架构,且是所有单元中最好的一个架构,低耦合高内聚相比于前三个单元做的比较好,解析UML过程中两轮遍历的设计非常实用,后续作业的迭代也比较方便,不需要做太大修改。我切身感受到了良好的架构对于迭代开发的益处。
测试理解与实践的演进
第一单元中,使用python编写测试数据生成程序随机生成测试数据,并手动构造了一些边界数据和针对容易疏忽的情况的的数据,并用sympy库检验正确性。成功地找到了自己和别人的bug。
第二单元的电梯调度程序调度方法因人而异,而且有多线程的随机性,难以检验正确性,手动构造数据难以覆盖所有情况。最后疏忽了测试环节,导致程序出现了大问题。
第三、四单元中输出确定,输入数据比较容易构造,可以自动化生成数据和别人对拍。也可以根据JML或要求的查询内容手动构造数据。
课程收获
逐渐形成了面向对象的编程思维。学会了java中的一些基本机制和常用的类的使用方法以及一些设计模式,代码能力有所提高。
建议
由于刚开始时对java还不太了解,第一单元压力比较大。对此可以适当减轻第一单元作业的难度或增加pre的内容。
增加实验提交时的反馈以及公布实验得分。