面向对象程序设计第四单元总结

一、架构设计

第四单元有三次作业,个人认为难度为第四单元的第一次>第三次>第二次。这主要和架构有关,在OO的作业中,架构的设计是相对最为花费时间的,每个单元的第一次作业主要就在设计和调整架构。

第一次作业

第一次作业中,我们面临诸如UmlClass这样的许多个类的时候难免手足无措,我的选择是化繁为简,对于所有的UmlXXX 这样的类,将它构造成自己的类,比如MyClass

这样做的好处有几个:首先我们可以将想要的信息保存下来;其次,我们可以为类设计方法,在为题目所要求的指令设计函数的时候,可以将函数作为这些类的方法来实现。具体保存的时候,考虑到Id对于每个Uml对象是唯一的,因此使用了一个HashMap来保存,同时加快检索的速度。

在读取的时候,我的选择是通过两阶段进行读取:

  1. 对于那些没有parent的Uml对象,将每个看到的保存进HashMap
  2. 对于那些有parent的Uml对象,将保存进HashMap中,同时通过检索到相关的父亲对象,然后将这个字对象加入到父亲对象内部的集合中。

第一次作业的架构如上。对于第一次作业的8条指令,还需要进行一些特殊的维护。

指令2需要求出类的所有子类,由于可能出现跨代的继承,我选择通过一次离线的“下推”将每个类的所有子类都求出来。在读取完之后,我会找到那些没有父类的类,然后将其所有子类的子类全部加入到自己的“子类集合”中,这个过程是递归的。

指令7需要求出类实现的全部接口,由于接口可能出现间接的继承,我选择对接口进行一次离线的“上推”,即对于一个接口,将其所欲的子类所实现的接口加入到“自己实现的接口”的集合中,这个过程也是递归的。

另一个会造成小麻烦的地方是指令5和6,考虑类的操作/属性的耦合度。它的麻烦之处在于有许多小的异常需要抛出,而这些条件需要很仔细的处理。比如只有当methodname的那个操作重复的时候才需要抛出异常,在其他不等于methodname操作重复的时候就暂时不抛出。

第二次作业

第二次作业反而比较简单,有了第一次作业的架构,同时不用修改第一次作业的代码,只需要把顺序图和状态图的类加入到原来的代码中就可以了。

同样,我会“重组”原来的Uml对象,放进HashMap中,在每个“重组后的对象”内部定义一些方法来,使主处理类询问对象内部的内容,以实现所需要的指令。

顺序图和状态图各有3种指令需要生成,可是大多数只是循环或计数就可以实现,不需过多介绍。值得注意的是“关键状态”这个问题,我的选择是循环枚举每一个点,考虑:”这个点会不会是关键状态“,将它先ban掉,然后跑BFS,如果跑不通则确实是关键状态。

第三次作业

第三次作业的思考复杂性比较大,但架构仍然和前两次基本相同,尽管增加了很多类。

第三次作业有一些异常的检测很容易,比如R001,R005、R006,R007、R008等。

主要的难点在于R003和R004,由于在第一次作业中题目数据保证了合法性,因此我的“上推”和“下推”可以正常进行,可是当数据有异常的时候,这个过程就需要重新考虑了。

对于R003,我的选择是:先无视可能有的异常进行上推,然后在需要的时候通过DFS来找环。这个“鸵鸟政策”其实是经过深思熟虑的,如果真的有环的话,那么“推一次”可能会出现多的边,但是在类和接口通过继承和实现关系所构建出的这个图中,不会出现新的环,同时原来的环也不会消失,因此照常找环即可。

对于R004,我的选择是,在推的过程中维护一个HashSet,如果推的过程中发现所继承的这个类或接口出现了多重继承,或者不同的子类中出现了相同的类或接口,则当前类是重复继承的。

二、架构设计思维的演进

所谓架构设计思维,我觉得主要是在讨论这样一个问题:当我们看到一个需要程序解决任务的时候,会如何来解决它?

在最开始的时候,我对代码的编写往往是先直接上手写,遇到问题就停下来解决,然后继续往前写。这带来的问题很明显:会经常走回头路,往往是先写——然后重构——再写——再重构,即使是这样,最后也很难获得一个理想的结果。

在几个单元的训练,包括解析表达式、多线程和最后的Uml解析中,我逐渐发现,上手写代码确实不应该是第一步,反而往往应该是靠后的行为。面临一个问题,最重要的是高屋建瓴的去认识这个问题:看这个问题的关键点在哪里,比如对于解析表达式而言,主要在于如何“保存一个字表达式的状态”;对于电梯调度有两个主要问题:一是如何分发请求,二是如何设计调度算法。如果看不到这的关键点,就很容易陷进无穷的细节之中,细节固然重要,但是只看见细节就会看不到这个System整体的样子。

第二个架构思维的演进,我认为是Trade-Off的思想。在以前,我或许只是想到一个能够Work的方法就万事大吉了。但是经过OO的课程之后,我认为这远远不够,需要的是选出最优解。当看到问题的时候,我们会想一些解决问题的方式,比如电梯这个单元,是针对性的设计一个算法呢,还是就用Scan算法呢?我觉得最重要的是要有意识的去做权衡,比如前者可能会带来更好的精确性,但是增加了编写的复杂度,我们是去舍弃一些精确性还是为了规避风险,减少复杂度呢,这些问题需要我们权衡,最终获得一个反复比较之后的架构。

第三个架构设计思维,我将其叫做设计的规范性和一致性。写代码的时候,天马行空并不是OK的,我们需要有意识的套用一些经典的规范,比如单例模式、工厂模式等,这不仅是为代码的正确性、可复用性做保证,也是能让这个架构被他人理解,提供可合作性的基础。

最后,所谓OO方法,看不见也摸不着,说起来像是一种玄学。OO是什么,是模仿现实世界里物体的性质,对于抽象概念的作用和属性进行建模,它是自然的,但也是复杂的。在计算机领域,有一句话这样说:“如果解决不了一个问题,就在它上面加上一个抽象层”。OO似乎也是这样,对于要完成的一个任务,我们所创立的对象就是那些“抽象层”,不管任务复杂与否,其中的子任务将被看作类的方法之一,这个任务交给这个新的类来处理就好了。从这个角度出发,我认为OO方法就是在分解我们所看到的任务,将每个任务合理地去分给类,类之间又有合理的交互,每个类通过被设计出来的“方法”相互协作,最终完成任务。

三、测试的演进

关于测试,我自己探索了以下几种方法。

以下的方法是我按照课程的时间顺序所使用过的方法,似乎也可以称之为演进。

静态观察

这也许不算测试,不过我觉得它最重要。总之当写完代码之后,我们需要反复地让代码在大脑里运行。

生成数据法

对于第一单元,可以通过自己生成的数据来测试。它在许多情境下是好的,唯一的问题是要求可能的数据出现范围很小。第一单元的输入中,变化再多也只是一个表达式而已,可是在第四单元就会发现这种方法还是有局限性的:Uml图是画不完的。

生成数据的思路往往需要结合“对拍”来实现,但是如果真正的面临了生产上的问题,很难有一个镜像的代码用于对拍,所以这样的思路确实不够好。

单元测试

我觉得这是OO的重要收获之一。

这种方法的好处在于,如果我们的写的代码模块性比较强,就可以分而治之,测试每个小模块即可。上面的生成数据的思想的问题在于,数据生成的范围太大了。如果能将问题变小的话,数据的范围便会成指数地缩小。

在具体测试的时候还是要依靠数据生成来进行,不过已经很友好了。我觉得这个方法很好的体现了“高内聚+低耦合”的必要性。实际上单元测试也是在生产过程中的重要方法,如果真要认真起来的话,在编写代码之前就应该写好单元测试的框架,不过在单元的作业中似乎有些太复杂了。

总之在这四个单元的课下测试中,我经历了一个无脑生成数据——碰壁——单元测试的变化。我发现单元测试确实是一个重要而实用的方法。

四、课程收获

  • 在第二节,我写了许多关于OO和架构的理解的收获,这是我在学OO课程中最主要的收获之一。

  • 其次,(不得不说)我学会了写一点Java。

  • 开始接触多线程编程,我觉得这个开始是非常好的,毕竟其他的课程比如操作系统,计组等很难讲到多线程编程,而多线程编程又十分的重要。

  • 开始了解和实践测试,生产过程中必备的方式。

  • 开始了解设计模式和代码规范,具体的程序不像是算法竞赛,代码的风格和规范都是有具体的要求的。

  • OO甚至帮我反思了一些我未来想做什么,我发现“写码”这个事情实际上是大有探索空间的……似乎System方向的研究更适合我一些?

五、课程建议

  • 希望取消互测。 OO课程真的很好,相比于大多数其他课程……我认为美中不足的地方就是互测。我经常想一个问题:互测真的有用吗?在完成单元作业的时候,同学们自己的代码会遇到Bug,又解决了Bug,所以在提交的时候都会是基本的Bug-Free版本,在互测的时候,大家有两个选择:

    1. 下载其他人的代码,然后仔细的Bug,这个过程很枯燥,在找自己的Bug的时候已经够折磨了。
    2. 自己造数据重复测试。

    实际上大多数人会选择后者,那么我不禁会思考:这带来了什么好处呢?似乎后者只是增强了一些造数据的能力或是DIY评测机的能力。

    可是如果想培养造数据或者写评测的能力,有许多更好的方法,比如新开一个单元。如果想要同学们分享架构,为什么不直接挑出来好的架构,让大家直接看呢?我了解有许多同学整个周日都在互测,他们也不太喜欢,只有一个原因:Score-Driven……

    有一个老生常谈的词是“内卷”,即无意义的内耗。我觉得同学们把大量的时间花在找其他同学们的错误上真的是一种内耗……如果能用这些宝贵的时间系统性地学习一些新的知识,比如就简单的阅读一本《设计模式》大黑书,该多好啊!

  • 希望修改第三章的内容。JML 是一个学术性较强的语言,可是我个人觉得在实际的应用场景非常狭窄,有一些像是为了匹配Java而出现的…… 或许第三单元可以出现一些更贴近生产的内容,比如设计模式,比如,造一个库?

  • 希望能系统性地讲解测试。当前课程的测试有点像是可有可无的,虽然我们总在强调测试的重要性,但是实践测试却没有被强制要求。我觉得理论课和单元作业都应该独立地来介绍测试,这似乎比JML等更有用一些。

posted @ 2022-06-28 01:40  RuiLinWho  阅读(37)  评论(2编辑  收藏  举报