OO_Unit4_Summary && Course_Summary
第四单元架构设计
由于三次作业是以严格的增量开发(类图 -> 状态图 + 顺序图 -> 前三种图的 Uml 规则检查)进行的,因此就只放上第三次作业的类图,以总结整个单元的架构设计。可以看到,从深蓝色的基层块(功能与结构都相对独立、简单)到浅蓝色的高层块(功能与结构越发复杂,存在与更基层块之间的层次分明的组合关系 Composition),整个架构能够比较好地体现出 Uml 结构本身的“树形结构”,当然,它的设计灵感本身也就来自 Uml 模型的树形菜单。因此,我个人对本单元的架构设计还是比较满意的。
本单元的主要任务是构造一个 Uml 解析器,上一次邂逅“解析”这个词语是在第一单元的递归下降解析表达式,所以我们其实也就可以自然而然地想到,对 Uml 的解析也是一个所谓“自顶向下”的过程,更何况 Uml 模型本身就有非常严格的层次关系。因此学习了 第四单元手册 中的内容后,对于读入的 Uml 模型元素,我们可以比较清晰地给出这样的解析思路:
可以看到,越“原子”的部分放在越后面解析操作,一旦某个元素存在对另一个元素的依赖,或者需要某元素作为承载它的“容器”,就需要将它放在被依赖元素之后进行解析。而解析的过程,实际上是将对应的元素创建为我们自定义的对应的类,并将它们进行层次化的组装,最后归结到类图、顺序图和状态图三个自定义类中。
在 MyImplementation 类中调用相应方法的过程,就是到对应的 Uml 图中调用相关方法的过程。而从上面的类图中,我们可以看到对应的 Uml 图中的相关方法也被层层“转包”到了更基层的元素类中去,最终回到像 MyClass 这样比较底层的类里去具体完成,虽然看起来存在着大量方法名的重复,但实现起来层次感比较分明,更能够体现出 Uml 模型的特点。下图是对上图解析过程的化归整理,可以看到解析的过程基本就是按照元素类之间的组合关系来进行的。
至于像 ReferenceType 这样的属性,如果搞不清楚原理的话最好阅读一下官方包代码中的 elements 文件夹,把其中的文件过了一遍之后会感觉对单元作业的实现过程豁然开朗。
本单元作业除了 Uml 模型的层次化特点意外,还考察了的可能就是一点点的图论算法和一些空间换时间的小 trick 了,当然,它们不应该是什么重点。
-
按名查找
由于 Uml 元素本身存在强标识性这一特点(从 id、name 等属性能够得到),因此对元素管理建立能够按名、按 id 查找的 HashMap 是自然的。而本作业中又大量存在着判断重复的要求,因此可以在 HashMap 中使用一个 ArrayList 来保存重复情况,比如:
private final HashMap<String, ArrayList<MyClass>> classHashMap = new HashMap<>();
-
循环继承
本单元作业对性能这块的要求其实还是挺低的,试了一下直接使用 dfs 也能够通过测试,当然也可以使用 tarjan 求强连通分量、拓扑排序寻找环状图这样的方法。我最后使用了拓扑排序的方法,难度不是很大。
-
Class 和 Interface
类和接口这两个 Uml 元素在行为和结构上存在着很多的相同点,因此我提取出了一些接口来统一这些行为,使得建模的过程更加清晰。事实证明用一个接口就可以解决问题,但一开始我没学清楚手册,担心后面还会有别的“用武之地”,这里显得代码架构冗杂了。
总体来说,这一单元的难度还是比较低的,主要就是注意细节,好好读指导书中对指令的要求,免得出锅。在这里悄悄吐槽一声,感觉本单元的手册和指导书的语文水平都需要小小地加强一下……(擦汗
葳蕤之道:本学期 OO 学习过程演进
随着第四单元架构的总结完毕,本学期的 OO 学习也将在此落下帷幕了。因此回头看一下后面的一串脚印,回顾回顾过去四个单元的架构设计思维,以及对面向对象思维的理解都产生了什么变化。
架构设计思维
第一单元:表达式解析与层次化设计
本单元的主要任务是对输入的表达式进行逐层解析,由于表达式文法本身存在逐层的形式化定义,因此引入的 递归下降解析 的思路对表达式进行处理。事实上这一单元是我体验上难度最大的一个单元——我能感受到它的意图是想要起到一个比较好的“Java 入门”作用,让层次化设计的思想从一开始就成为大家的 Java 面向对象程序设计思想钢印。而它也确实做到了这一点。
引入了递归下降和表达式文法的形式化定义后,自然而然地将多项式(Polynomial)作为一个原子多项式的HashMap来进行托管,递归下降计算与化简的过程,就是不断地对程序入口类 MainClass 中构造的那一个多项式对象进行原子成员的更新,仅此而已,这种层次化构造使得增量开发的过程变得很自然,不至于出现大面积重构这样的悲剧。有不少人在知乎诟病过第一单元把递归下降的地位看得太重,但我倒不太这么想,它的引入以一种相对比较自然的方式把层次化设计的思想介绍给了诸位初学者们,帮助我们建构了一个比较良好的面向对象思维基础。
偷了第一单元博客的图来说明这一点:
可以看到,这就是一个比较具有层次感的下降解析、有序递归过程,而让我为之感激的是这种层次化设计的思想也成功地陪伴我直到 OO 作业的最后一个单元。这一单元作为入门的一个单元,也让我很好地体会到了面向对象多态的特点,之前在 C++ 开发过程中的体验并没有那么深刻。Java 语言本身的各种方便之处,亦让作为初学者的我收益颇丰。
第二单元:电梯与多线程
相比上一次作业,我在本单元中更多地感受到了迭代开发的条理性与设计模式的工整性,虽然与真正的工业生产相比必然不足为道,但我似乎真的窥见了某种成体系、成规则的构筑代码的自然方法,某种将会贯穿于我未来职业生涯中的自然方法,“工厂模式”中工厂二字代表的标准化,正在逐渐被我体会。相当明显的一个表现就是,后两次作业几乎都只需要在前一次作业的基础上做出很小的改动与增量开发,就能够很快地“通关”,即使遇到了新需求,也能够相当自然地从已有模块或接口出发进行延展。
当然,更大的收获来自对多线程编程中线程安全部分的学习,电梯运行策略倒是其次(只不过我的电梯策略选择不当使得我损失了一些强测分数)。这一部分我吸收了操作系统理论课有关部分带来的灵感,由于线程之间存在对临界区的访问,因此必须保证访问严格互斥,在 Java 中我们可以采用同步块与锁来解决这个问题。
线程不安全带来的幽灵问题困扰了我无数个夜晚,而在解开它们之后的获得感又使人无限满足。
在本次作业中我主要使用的是生产者-消费者模式。关于设计模式的部分,我想我在实验代码中反而体会得更多,比如标准的生产者-消费者模式、主从模式、流水线模式和单例模式等,它们也给了我的作业许多灵感。当然,《图解JAVA设计模式》一书也给了我不少帮助,尤其是在理解流水线模式的部分。希望今后自己可以更标准地利用相应的设计模式,以规范思维和代码行为。
值得一提的是,本次作业中我还有一个教训深刻的体会:在困难面前不能排斥重构,不能排斥重构。
第三单元:社交网络与 JML 规格
这一单元是实现上最轻松的单元,JML 契约式编程的规格化特点使得我们对要求的理解不会出现歧义,本单元所有的问题(包括架构设计)都可以用“请仔细阅读 JML 表述”来解决,因此三次作业的架构都是与之强相关的。整个单元需要注意的点其实就是性能上的考虑以及自己书写 JML 约束时的考虑过程。前者更像是对图论算法的复习与考察,因此不再赘述;而后者则让我更好地体会到了建模语言的精妙之处——从建模语言转化到代码是自然的,那么如何使得自己写出来的自然语言转化为没有歧义的建模语言呢?
第四单元:UML 解析与 UML 模型
本单元由于 UML 模型本身树形结构的存在,架构自然是非常清晰的,具体细节可以从第一部分看到。将 Uml 元素分门别类地转化为元素类,并且再根据它们之间层次分明的组合关系进行顺序解析,就能够很好地完成这一单元的任务。在这一单元的任务中,我也感受到自己的面向对象思维逐渐成熟,尤其是从第一单元开始就深入人心的层次化设计,从难度的角度出发,我认为它和第一单元更应该对调一下顺序,使得初学者能够从自己手中创造出更工整漂亮的架构设计,强化对类与类之间关联关系的认识——反正每次博客都需要绘制 Uml 类图的嘛。
OO 思维理解
- 计算机是头脑延伸的工具,同时还是一种不同类型的表达媒体。这种工具看起来已经越来越不像机器,而更像我们头脑的一部分,以及一种如写作、绘画、雕刻、动画、电影等一样的表达形式。面向对象程序设计(OOP)便是这种以计算机作为表达媒体的大趋势中的组成部分。
这是我在《Thinking in Java》一书中摘抄过的一句话。确实,面向对象思维比起一种程序设计需要遵守的方法,更像是一种表达方式,有的时候还能成为一种生活哲学:它是一种不局限于计算机世界的普适性思维方法。
我们知道,编程语言有很多范式:面向过程、面向对象和函数式。函数式,基本是以数学方式来看待这个世界;面向过程看待世界的方式,是这个世界只存在数据和逻辑,数据只需要根据逻辑进行修改;而面向对象,则是对现实世界的一种映射——我们可以认为“白马非马”:“马者,所以命形也;白者所以命色也。命色者非命形也。故曰:白马非马。”,“马”会被我们自然地抽象成一个基类,甚至说 Interface,在公孙龙的口中这叫做共相,而“白马”当然是对它的继承,是殊相。只不过在面向对象的角度里,完美的“共相”是可以存在的,这就比这篇先秦短文的层次要更高。面向对象和形而上学之类的东西一样,都是对现实场景的抽象,可以是我们认识世界的方式,也可以是我们与世界“交互”的思维指导。
毕竟“万物皆对象”的思想就强调:“理论上说,可以抽取待求解问题的任何概念化构件,将其表示为程序中的对象。”对象其实就是问题空间中的元素及其在解空间中的表示,也即,程序可以通过添加新类型的对象使得自身适用于某个特定的问题——OOP 是一个根据问题描述问题并解决问题的过程。
Thinking in OOP is important.
封装与解耦
我们在 OO 的第一节理论课就学到过,面向对象的底层逻辑其实就是逻辑联系紧密的东西需要聚合在一起,因此我们使用对象的思维去处理这些“聚合体”以及它们引申出的各种问题。聚合体或者说模块的内部可以保留一些秘密,不暴露给无关人士,而对于需要与之交互的对象,则提供一些接口给它们使用。这种思维其实很早就体现在了我们的程序设计生涯中:在 C 程序设计中,我们常常设计一些复杂的结构体,希望把一系列具有逻辑关联性的东西合到一起去;在 C++ 开发中,我们更能大胆地使用 class 的概念去处理问题,只不过 Java 语言将这种思维发挥到了纯粹的极致。
从这里,其实就可以引出抽象这个概念,“把 ... 抽象成 ...”已经成为了可以随口说出的话。在面对问题时,提取共同点抽象成“class”、把不关注的部分“封装”起来、只暴露需要的“接口”进行“模块化处理”的过程或许是我在 OO 课程中得到的最大进步。它不仅是下面要说的 层次化设计开发 的基础,同时也应该是我们处理生活中所有问题的底层逻辑。
层次化设计开发
层次化设计是我在四个单元作业中吃到的最大的“甜头”,亦是封装 / 模块化思想的最佳表演。从课程作业到工业生产,从程序开发到芯片设计,层次化设计与增量开发都是亘古不变的主题,既能够简化大规模设计,也能够理清架构设计思路。树形图、流程图是陪伴我走过整个 OO 课程的好伙伴,也被我应用到了 UI 设计、软件开发等等经历中。层次化设计的思想能够使整个架构看起来更加张弛有道,极大地加快开发速度,也能及时暴露出不完善的地方。
因此,动手之前,不妨先动笔画画层次模块结构图——这是 OO 思想给我带来的一大收获。
设计模式
设计模式比起面向对象思维的核心,更像是它的引申运用,当然其中的奥妙也是特别多的,理解各种设计模式可以帮助我们建构更合理更高效的架构设计。我们需要做的其实就是在这一次次的实践中了解、熟悉它们,并能够将它们运用到今后的生产生活中去。比如,需要“宏观调控”时,采用单例模式;需要“逐级负责”时,采用流水线模式或者生产者消费者模式;需要处理明显的树形架构时,采用组合模式或者迭代器模式等……它们的方便以及实现基础都仰仗于对象这一聚合概念的被抽取,因而能够处理非常接近现实生活且场景经典的问题。
测试理解与实践
一个有效的测试过程应当有以下的处理过程:
- 宏观覆盖
- 按照设计需求将输入分类,形成输入数据层次树,同时根据设计要求,设定每个层次的数据范围,注意要覆盖到常规数据和极限数据。
- 通过输入数据层次树生成覆盖数据,覆盖率是测试的重点。通过覆盖测试找到 bug 的存在区域 / 层次,并定位到微观查找中。
- 微观查找
- 从宏观覆盖得到了 bug 的产生范围之后,根据程序具体的结构和算法逻辑,在该范围内手动构造特定的数据进行针对性测试,提起测试强度。
- 考虑极端数据和性能优化问题,构造针对性数据。
当然,还需要分析程序所使用的输入处理结构、类库等,即使在测试中正确也要了解到它可能存在的局限性和不适用场景,在这里可以根据分析结果构造相应的输入特征对其进行处理。
得益于 OO 程序设计层次化开发的思想,构造输入数据层次树的过程是自然的。我也从第一个单元的单打独斗、手动白盒测试,一步步走到了能够独立写出覆盖率比较高的数据生成器和对拍机这一步上,对于上述的测试思路,我实践得相当不够充分,因此在今后的学习生涯中希望能够把握住更多的尝试机会,强化测试思维。
课程收获
我觉得正如我在第二单元心得体会中写到的那样,我从 OO 课中才真正体会到一种更加“工业”的开发体验,这就是为什么我在完成第一单元学习时感受到了一股强烈的游戏开发的既视感:阅读设计要求、模块层次化设计代码架构、增量开发、覆盖测试等等过程都与我的游戏开发经历无限匹配,也能够提升我这方面的开发能力。OOP + 多线程的教学安排对我们来说是一个很好的工程训练过程,相信也是很多同学对工程开发的初窥。只此一点,本课程就给我带来了很大的快乐。
当然,它给我带来的更大收获在于对面向对象思维的体会理解,具体的内容已经在上上节中详细说明过了——Thinking in OOP is important,面向对象思维更应该是程序员们处理问题的人生哲学,面对问题时始终要基于一个良好且经过深思熟虑的层次化架构进行增量开发,以免日日有蘧瑗知非之悔,不过,也永远不要排斥重构。
最后小小地感谢一下 checkstyle 的存在,被规范了一学期的代码风格后,回看以前的作业代码感觉好像在看压缩饼干。
课程改进建议
- 第一单元和第四单元的学习内容对调,或者采用 4 -> 1 -> 2 -> 3 的顺序。一方面使得初学者更好地体会层次化设计的特点,通过 Uml 元素的树形架构加深对“万物皆对象”思维的理解,同时还顺便引入了一开始也就得使用的 Uml 类图;另一方面降低入门难度,使得课程难度的递进关系更合理。
- 每次实验上机后希望能够公布正确答案或者公布测试分数,总之需要给点反馈,否则上机的作用除了给出可借鉴的设计模式代码以外感觉非常微弱,这不就脱离了上机的目的吗……
- 给出比较完备的课后学习资料,比如有关 Java 程序设计、设计模式、多线程的一些书籍,有关递归下降、JML 契约式编程、完整的 UML 规格(网上真的很难找!)这种额外知识的补充资料,提前一周或者多少发放给同学们,既能够适当降低单元的考察难度,也能够更好地向同学们介绍面向对象思维与设计模式的内涵,使得训练的重点回归到我们的课程名中来。本课程被一些人戏称为“编译 pre”、“算法设计 pre”不是没有道理的。
- 在实验或者作业中引入对 JVM 垃圾回收机制、内存机制或类似行为的学习与实践,感觉比较有趣。或许可以考虑以此代替第三单元的社交网络?