301
社区成员
发帖
与我相关
我的任务
分享学习了前三个单元后,我们已经基本了解了面向对象的面向数据和行为抽象和面向并发和协同抽象,以及基于规格的层次化设计结构。既然了解了基本理论,那么就应该学习面向对象设计的工具和方法了。这个工具就是目前已经广泛使用的UML!
接下来,笔者就从UML的使用角度来总结这一单元和整个学期的学习。
在经过的第三单元的作业后,笔者充分感受到设计与实现分离给实现者带来的快感。随意笔者最初也希望像第三单元的JML一样,规定好每一个类的职责,并且设计好每一个类应该管理的属性和应该提供的方法,最后再实现代码。
然而,在设计好类和架构,并大致规划出每个方法的作用后,我在开始正式写代码后才发现,我的设计纯属纸上谈兵! 在笔者最初的设计中,有的类毫无用处,有的类承担了不应承担的责任;类中的属性和方法也是这样。
笔者不得不抛弃本来在UML中的一些设计,并且只好边写代码边设计架构,完全背离了原本设计与实现分离的初衷。
经过思考后,笔者尝试分析设计与实现分离失败的原因。虽然说菜是原罪,但是我也尝试明白我究竟菜在哪里——
这是感触最深刻的原因了。因为第四单元作业的开放性,众多架构都能够满足作业要求。
例如,单个图书馆类解决一切问题是可行的;
例如,为借还处、预约处和书架都设置一个类也是可行的;
甚至在上述基础上设置多线程都是可行的...
那么,在所有可能的架构中应该如何选择呢?这个问题在我最初设计的时候总是环绕在我的心头。如果最终选择的架构不能够满足作业的需求,那怎么办?
我想,或许厉害的架构师能够利用ta充分的经验和学识很快就选择好一个合适的架构吧!
前文提到,笔者最初的设计中,有的类毫无用处,有的类承担了不应承担的责任。这应该是笔者臆想程序运行过程而未结合实际输入输出的结果。
具体来看,笔者最初认为,既然图书馆要管理书籍,那么应该设计一个Book类!所以笔者将Book类信誓旦旦地放进了UML类图中。然后在观察了官方包后发现,官方包直接通过HashMap<LibraryId, Integer>数据结构来管理书籍,根本无需再构建特别的类!
所以,如果让笔者再一次尝试构建一个架构,那么笔者会考虑从输入到输出的全过程,通过思考全过程中需要什么操作来划定类和方法的职责。
因为笔者最初设计的失败,所以在最初画出UML后笔者就很快放弃了先设计再实现的路线,而不得不将设计与实现同时进行。这样,笔者还是在代码成型以后再逆向画出最终的UML图,仍与设计和实现分离的初衷背道而驰了。
不过,笔者自认为自己还是在一次次代码作业中逐渐接近面向对象设计和构造的真谛了——
笔者仍然记得,笔者第一单元的尝试是明显失败的。第一单元的作业,我完全是按照面向过程的思想来完成的,全程没有考虑任何类和方法应该完成的职责,完全根据每一步需要做什么来设计一个函数。至于函数放在哪个类中,就取决于怎样访问属性方便了。
纯面向过程思维的一个体现,就在于我认为,类中属性的可见性为public应是理所当然的。CheckStyle为何要要求属性的可见性为private?很容易理解当时我为何会提出此类问题:C语言里的结构体内的成员变量应该对外界可见,不是很理所当然的情况吗?
在第一单元总结后,我开始充分意识到我没有理由继续坚持面向过程的设计原则了。在第二单元我必须开始尝试采用面向对象的设计思想!
不过,第二单元的作业是多线程的架构设计,其工作量较第一单元要更大,这对在第二单元实践面向对象的原则的我是一个相当大的考验。
令我相当自豪的是,我成功通过了这样的考验,开始理解面向对象的基本原则了——当我回顾第二单元作业的代码时,我惊喜地发觉每个类已有明确的职责,每个方法不会再越权负责不应负责的部分。特别是第二单元的第三次作业中,我发觉我能够通过继承来实现迭代,完美符合OCP原则并且工作量大幅减少时,我开始觉得使用面向对象原则来编程有些舒服了:joy:。
当然,这并不意味着我已经掌握了面向对象的真谛。回顾代码时,我仍然能够发觉代码中有以下诸多小缺陷:
对容器内元素的迭代不规范。
当需要迭代某容器时,我直接将对象的引用返回给外部进行操作,这会无法保证外部操作后容器内元素是否仍合法。
继承关系不满足依赖倒置原则。
第二单元第三次作业中,我虽然通过继承实现了代码的迭代,但这构成的继承关系出现了高层次类依赖于低层次类的情况,致使其中一个继承关系仅用于引用类型的兼容,完全不复用父类的任何方法,浪费了内存空间。
并发安全性的实现并不统一。
设计与实现并不分离,写代码仍很困难。
虽然同学们对第三单元的JML颇有微词,但笔者却颇觉享受JML带来的便利。JML完全规定了每个类的属性和方法,程序员只需要简单地写代码实现JML即可。
当然,笔者最初并未尝试进行任何算法上的优化,因而第三单元第一次作业的得分并不高。不过,在笔者开始尝试进行算法优化后,JML提供的设计与实现分离的优势就体现得淋漓尽致了——作为实现者的我完全无需担心类的职责和类之间的协作关系,只需要一门心思研究算法的优化即可!
有良好架构的加持,算法的优化也变得异常顺利,第三单元可谓是相当舒服的一单元。当然,理解JML的形式化语言还是需要一定时间和精力的。
恰如前文所说,笔者自认为自己还是在一次次代码作业中逐渐接近面向对象设计和构造的真谛了。即使设计与实现并未完全分离,笔者在完成第四单元的每次作业时,也仍然能够感受到使用面向对象原则的舒适感。
这具体体现在,我开始学会根据每一个需求完全地划定每个类中方法的职责,并根据这完全明确的职责实现每一个方法了。
例如,一个
QUERIED的图书馆请求会被分派器分派给书架或读者信息中心,书架或读者信息中心则输出需要查询的信息。
于是,我会在分派器中设置serve(LibraryRequest request)方法进行分派,在书架或读者信息中心中设置serve(LibraryRequest request)方法进行输出。
用这样的方法完成第四单元代码的编写后,我发觉我的每个方法拥有相当明确的职责,方法名称可以完全概括方法的职责。不知为何,这样的明确让我觉得相当舒适。
在前两个单元里,笔者非常关注面向对象原则在编程过程中的应用。但在第二单元结束后,笔者面对互测反映出的完全未曾注意到的bug,提出了正确性方面的问题:如何判断面向对象的程序的正确性?
笔者对于测试并无太多考虑,只能针对笔者采用过的两种测试策略进行简述——
JML很好地解决程序正确性的问题,而Junit则依托于JML,很好地测试程序中各个数据实体中数据和方法的正确性。
然而Junit测试也有代价——其编写的确比较复杂:程序员需要自己构造测试数据,自己根据JML重新实现目标方法,最后要做数据的比较。当测试数据集合比较庞大时,构造测试数据比较麻烦;并且最后进行数据的比较也比较困难,特别是容器之间的比较,如果要求严格的对比,则需要对其中元素进行逐一比较,相当复杂。
但是,笔者仍然认为Junit是面向对象的测试,是很符合面向对象原则的。如果能够解决JML形式化的困难,那么Junit是极好的测试工具。
除了第三单元,其余单元作业的正确性验证很大程度上都要依赖于学院内高水平同学开发的评测机。
评测机基本便采用的是黑箱测试了,在此便不多赘述。
如果计算机组成为我正式打开了计算机的大门,那么面向对象设计与构造则将我带入了更加奇妙的计算机世界,并且极大地提升了我对计算机科学与技术的兴趣。
作为社会主义者,笔者投身于计算机领域并不为名利,只望能通过科学技术的先进生产力为社会主义的未来贡献力量,愿在短暂的一生内见证人民过上幸福的生活。然而社会主义理想却在复杂的现实环境中难以立足,颇遭他人轻视。故笔者深知要实现理想,必须从现实开始思考和实践,而并非总是天马行空地臆想。OO课程则让我开始接近于真正的计算机领域,我开始逐渐明白行业中的诸多事实,得以将现实与理想进一步地结合。
虽然笔者尚未完全掌握面向对象中的各个细节,但即使如此,OO课程对笔者的引领作用也远非这段言辞所能穷尽。将来笔者仍将怀揣这份社会主义理想,继续探索科学技术的世界。