OO 第四单元博客作业
第四单元架构设计
类图
-
4_1
-
第一次作业比较简单,只需要解析类图;
-
对于解析,需要对类图中每一种元素解析,需要用到
instanceof
判断UmlElement
类型。
-
-
4_2
-
较第一次增加了对顺序图和状态图的解析,与第一次基本同理;代码细节较多,但是逻辑上理解不困难。
-
-
4_3
-
增加了对三种图的合法性判断
-
本次作业中涉及了大规模的重构,因为之前三种图的所有元素都存在
MyImplementation
中,本次作业仍这样处理会很麻烦,因为不能分图的类型去判断相应异常,而且会造成类超过 500 行。
-
架构分析
需求理解
本单元要求实现的是 UML 解析器,即对类图,顺序图和状态图的 .uml
文件中提取出特定的信息,支持处理不同的异常类型和判断其合法性等。
实现思路
三次作业的需求是逐渐增加的,且思维难度不高,逻辑性强,我认为是四个单元中过渡最平滑的一个。
核心问题是如何判断什么种类的 UmlElement
需要自行封装。我的理解是,逻辑上包括(或者说拥有)其他元素的元素需要自行封装,如 UmlClass
中有 UmlAttribute
和 UmlOperation
,但是官方包中并未给出 UmlClass
直接获取这两者的方法,这就需要我们通过在 MyImplementation
类中实现相应的解析之后,才能够找到并存储这些元素的父子关系。写代码时经常需要下面三张图:
-
类图:
-
顺序图:
-
状态图:
对方法调用的处理也很重要。比如在第三次作业中,我把三种图都封装成了相应的类,对于 MyImplementation
并不是实际存储了这些 UmlElement
解析后的内容,逻辑上他更像是一种提供交互的接口,如对于 getClassOperationCouplingDegree
的实现:
-
在
MyImplementation
中:只调用classDiagram
的同名方法
-
在
classDiagram
中:处理类名对应的异常后,直接调用该className
对应的类的同名方法
public List<Integer> getClassOperationCouplingDegree(String className, String methodName)
throws ClassNotFoundException, ClassDuplicatedException
, MethodWrongTypeException, MethodDuplicatedException {
classCheck(className);
MyClass targetClass = classes.get(className).get(0);
if (targetClass.checkWrongType(methodName)) {
throw new MethodWrongTypeException(className, methodName);
} else if (targetClass.checkSameParaList(methodName)) {
throw new MethodDuplicatedException(className, methodName);
}
return targetClass.getOperationCouplingDegree(methodName);
}
-
在
MyClass
中:基本实现这一方法的内部逻辑,但是需要调用MyOperation
的同名方法,最终返回结果
public List<Integer> getOperationCouplingDegree(String methodName) {
List<Integer> res = new ArrayList<>();
if (!name2Operations.containsKey(methodName)) {
return res;
}
for (MyOperation op: name2Operations.get(methodName)) {
res.add(op.getCouplingDegree(umlClass));
}
return res;
}
-
在
MyOperation
中:实现最底层逻辑,返回结果
public int getCouplingDegree(UmlClass umlClass) {
int res = 0;
HashSet<String> set = new HashSet<>();
for (UmlParameter parameter: parameters) {
if (parameter.getType() instanceof ReferenceType) {
String referenceId = ((ReferenceType) parameter.getType()).getReferenceId();
if (!set.contains(referenceId) && !referenceId.equals(umlClass.getId())) {
res++;
set.add(referenceId);
}
}
}
return res;
}
对所有单元的总结
架构设计思维
-
第一单元的主题是表达式的解析和计算。本单元很好的利用了
Pre
中学习到的面向对象特性。难点在于对递归下降的实现和理解,即“表达式-项-因子”这一结构。比较有意思的扩展是自定义函数和求和函数,我是利用类似现实中“代入未知数”的处理方式,即用实际参数替换形式参数,这样实现的难点不在于字符串替换,而是要解决括号嵌套。第三次作业的需求恰好就是支持括号嵌套,这也提醒了我好的架构不仅仅会避免大规模重构,更会让后续的迭代方便很多。
-
第二单元的主题是多线程电梯调度。现在来看,这一单元思维难度其实不大,重点在于对 java 中线程的理解。如何合理地设置同步块、调用
notifyAll()
、如何避免死锁和轮询......等等,对于这些问题的处理,很大程度影响了本单元作业的正确性与性能。本单元我也学习并应用了较多的设计模式,体会到了其功能之强大,如利用单例模式创建调度器避免其作为参数多次传递、利用工厂模式更方便的创建不同的电梯对象,利用消费者-生产者模式理解电梯与乘客、调度器的关系等。 -
第三单元是实现社交网络系统。本单元其实不用深入理解项目需求是什么,很多地方根据 JML 规格就可以编写出正确的代码。
-
第四单元是实现 UML 解析器。本单元最好地诠释了面向对象中封装性的强大。这一单元要求我们精确地理解 UML 模型中各个元素的含义和父子关系,引入了属性耦合度、继承深度等概念进一步提高要求。什么时候需要封装?如何进行方法调用会尽可能地方便?这都要求我们对封装性有深刻的理解。
测试
-
第一单元涉及数学知识,可以很方便的手动构造边界数据;
-
第二、三单元更适合编写数据生成器生成随机数据+对拍进行测试。其中,第三单元的逻辑非常适合自行搭建评测机。
-
第四单元虽然也适合写评测机,但是逻辑上难度不大,细节上问题较多。更适合采用
JUnit
手动测试
课程收获
-
让我第一次认识到了不需要系统学习编程语言的语法(虽然 Java 语法也不是这门课的重点),也可以写出正确且可读性强的代码(
可能是IDEA功能太强了?) -
在本门课程中完成了面向过程到面向对象思维的转变,最明显的就是从最开始在第一单元使用
public static xxxx
方法作函数,到最后第四单元对不同函数进行不同层次的调用和封装。 -
认识到了测试和 bug 修复的重要性。既不要忽视细节,也不要过分注意细节导致大局观出现问题。
-
认识到了规格化设计的重要性。如利用 JML 语言实现对前置条件,后置条件等的预先考虑。
-
可以自行完成类图、顺序图、状态图的绘制,并深刻理解了他们的概念与意义。
-
可以使用 git 完成代码版本管理。
-
复习了图论的一些算法,对于算法和数据结构的理解更深一步。
课程改进建议
-
可以多增加一些业界大佬讲座,想听更多前沿的并有实际应用价值的知识。
-
课上对作业需求的解读较少。很多时候都是课上的内容可以理解,但是对作业一开始总要蒙一段时间,不知道如何应用课上知识。而且作业内容都是放在每次课的最后讲,用时很短。
-