zoukankan      html  css  js  c++  java
  • 面向对象第四单元总结与心得体会

    一、第四单元作业设计

      (一)第一次作业

           作业架构:

           可以看到其中有两个类比较臃肿,一个是我自建的保存UmlClass的信息的MyUmlClass类,一个是实现的接口。出现这个情况的原因是,我的逻辑主干架构被分为了构建和查询两个部分,接口实现的类承担起了构建和套皮的职责,而查询的任务我几乎全部交给了MyUmlClass。

          体系部分逻辑解析:核心思想是用直接连接的方式代替Id连接。根据需要,我自建了MyUmlInterface、MyUmlClass、MyUmlAttribute、MyUmlOperation和MyUmlAssociation这5个类。其中,MyMulInterface中有用的储存信息是直接继承的父接口(ArrayList)和直接间接继承的所有接口;MyUmlClass用于适应各类查询的需求的同时也作为此次作业的核心,保存的有用的信息有父类、祖先、自身的MyUmlOperation和MuUmlAttribute以及所有继承的MyUmlAttribute的三种存储方法:ArrayList、按Id索引的HashMap和按名字索引的HashMap,还有四个记录不同种类操作数量的int;MyUmlAttribute是专门用于适应查询需求的(也就是说没有查询需求就没有这个类);MyUmlOperation和MyUmlAssociation都是工具类,一个用来保存下属的UmlParameter,另一个用于保存下属的UmlAssociationEnd。

          输入部分逻辑解析:为了避免对输入元素的浪费,我采用了一种“试探性加入”的方法,对于输入的每一个元素都试试看能不能加入,如果能,则加入,否则将其丢到缓存区中,等待下一次查询。这种查询方式的好处有两个方面,一方面我不必整体把握,不必想着我的体系中应该先加入什么后加入什么,只要明确每一类元素加入的条件,就能做到构建体系;另一方面,这种试探性加入的方式相比于简单循环可以减少查询的次数:如果所有的元素都是按照逻辑顺序加入的,那么我的方法甚至不需要循环,查完就加完,而按逻辑顺序确定先后加入的方式则会造成大量的浪费。在一般情况下,我的算法优于按逻辑循环,而在极端的条件下,我的算法也不输按逻辑循环。此外,为了减少时间开销,我从java的垃圾回收机制中获得灵感,开辟了两个缓冲区,相当于java存放有一定年龄的新生代对象的缓冲区,交替存放,并且像java那样对元素进行筛选。同时,为了很好的处理UmlParameter和UMLAssociationEnd的问题,我专门设置了两个工具容器,分别存储MyUmlOperation和MyUmlAssociation,直接将有可能存在下属的元素存放起来以备咨询。

          查询部分逻辑解析:核心思想是查询驱动,缓存支持,连带查询,一带一路。查询驱动的意思是,我很懒,除非我必须查询,否则我什么也不会做;缓存支持就说明这次作业我利用了缓存技术,因为这个单元的作业太特殊了:输入和查询完全是分开的,查询一旦开始就意味着现有的体系不可能再被改变了,这种情况下缓存是绝好的方式;连带查询其实是一种预判,因为很多查询指令都会涉及到类的继承关系,而每次查询都要沿着这条路上去的话,可能会造成一定的时间浪费,因此我干脆把这些东西一起连带查询了,说不定以后会用到;一带一路的意思是,一旦启动了关于继承的查询指令,其所有的父类的所有继承相关的内容都会被递归式查询并一路记录,配合前两条来看这样的方式很有意义,因为有可能我多次查询都查询到了一个类的后代上但是查询的内容却不尽相同,连带查询和缓存技术可以保证时间开销的减小,同时这种一带一路的方式也不浪费查询的结果:先前查了父类,然后再查子类,则子类查到了对应的父类就得到了信息可以准备返回处理自己的信息;先前查了子类,然后再查父类,父类已经被查询过了,信息全部都有了,直接向外界传递信息即可。具体来讲的各类查询方式中,有一类比较特殊,那就是查询类是否违反隐藏性原则,这也是MyUmlAttribute诞生的原因,因为查询要求attribute要“认主”,而通过一步步查询父类显然又浪费了查询结果,因此我干脆设立MyUmlAttribute来直接让attribute认主,而各个类只管保存和继承这些认主的对象即可。

          这么复杂的缓存其实是有代价的,空间开销是一个方面,另一个方面是,我大胆地使用了对象和对象的引用,,就要承担带来的风险,结果就是,我一不小心将类自己的按名索引属性浅拷贝到继承按名索引属性(储存方式是HashMap套ArrayList),结果就是类自己的按名索引属性也可以被修改,然后直接导致了一次无效作业:D不过好处就是我之后两次作业变得非常轻松,因为不用担心时间问题,而且缓存机制还储存了相当的信息,对第三次作业产生了一些积极的影响。

          方法分析:

           大概是因为只能分情况然后产生了大量的if-else语句吧。

      (二)第二次作业

          作业架构:

           怎么说呢,这次作业总给我一种相当不爽的感觉,因为我所做的工作起码有一半都是以备咨询,完全没有在作业中体现出来,不过为了保证体系的完整性,我还是选择将这些无用元素保留了下来,至于体系的构建和查询的技巧也乏善可陈,该说的思想之前一次作业就说过了。总之这次作业就是很无聊,不过也证明了我第一次作业的体系构建的还是比较高明的,比如输入的处理,有什么元素加上去就是,不必多虑。

          方法分析:

           似乎依然是因为if-else太多了(MyUmlState是因为同时可以看作初始、中间、结束状态所以if-else多了)

      (三)第三次作业

          作业架构:

          没什么太大的变化,就不放了

          分析处理:注意到R001到R004都要求具体返回一些信息,R005到R008不要求具体返回信息,因此R005到R008的检测全部在构建的过程中进行,一发现有问题马上通过抛异常的机制层层上报,到主交互接口实现类中以errorCode的方式记录,同时立刻结束输入以节省时间。异常查询时首先判断errorCode,而后再对R001到R004进行判断。

          对R005到R008的判断:R005发现符合条件的元素没有名字立马抛异常(注意UmlAttribute,有可能并不属于类图,这种情况下是不抛异常的);R006发现一个UmlAttribute属于Interface但不是public即抛异常;R007发现Source的MyUmlState是FinalState抛异常;R008发现Source是初始且已经有迁出抛异常。

          对R001到R004的判断:R001输入的时候就做好准备,专门在一个类中开名字域(因为抛异常返回信息中只要求名字,这样就不需要管名字的来源),以备后续的查询;R002到R004要严格按顺序执行,顺序控制放在主交互接口实现类中完成。R002的查询分两种:类和接口。对于类而言可以采用缓存机制,具体来讲,对每个类都要外部查询并通过static的属性记录,而后内部按父类溯源查询,一旦发现父类为static记录,则一路回退并记录环上元素;否则若发现已被查询,则一路回退并销毁static记录(当然可以不必销毁,下次查询直接覆盖)。在这个过程中注意不要还原查询记录。这种特殊的查询方式是由单继承决定的,从图的角度看,每个节点只有一个出度,那么如果节点不在环上,则必然会有一个终点,因此一路查询的方式是可靠的;此外,如果一个节点的后继节点已经被查询过,则存在两种可能性,一种可能性是后继节点在环上,另一种是不再环上。若后继节点在环上,则本节点就不在环上;若后继节点不在环上,那么最终追溯到底一定存在终点或者追到环上,无论如何,本节点一定不在环上,因此缓存是可靠的。至于接口的多继承,其实也简单,多遍扫描,每次都刷掉没有入度或者出度的节点,直到图的大小不再发生变化,此时剩下的阶段全部在环上。实际上这应该是下下策,因为这种算法一来要依靠已有的单向继承构建双向联系,二来要一遍遍地刷,非常浪费时间,不过我图论并不好,而且不想思考,加上这么浪费时间的事系统只干一遍,想想还是可以接受的。R003要依靠R002的结论,那就是没有环。一旦出现了环,那么整个算法就会失效。具体来讲,重复继承依赖于多继承,因此类可以不用考虑了。接口也变得很简单,只要看看接口自己直接继承的接口的所有继承接口(约定所有继承接口包括自己)之间有没有重复就行了,而这件事我早在第一次作业就写完了,我唯一要做的就是在有重复的时候将调查的接口标记一下就行了。(后来我才发现这个特性是可以继承的,也就是说父接口出现了重复继承,子类一定是重复继承的,这导致了我一个bug,解决的方法是发现直接继承的父接口被标记了那么子接口也被跟着标记)R004的判断也类似,不过接口的判断已经完成了(R003先于R004检查),只要顺着继承链走就行了,一样是在原有的if语句补一个标记用的else分支即可。

          方法分析也不放了,说来说去还是if-else的事

    二、从四次单元的架构设计分析个人OO方法的理解和演进

      第一单元:初次理解OO

          第一单元的任务是构建函数解析器并对函数求导。我最终的架构其实并没有完成,不过足够应付三次作业了。简单来讲,完成的部分是对于输入表达式的解析、简单化简、求导和输出。在作业中,我建立了表达式、项、因子三个层级的类,每个类都设有求导和转字符串的方法,同时建立了因子的接口实现抽象的目的。就这样,我实现了对象的封装,同时也理解了继承、多态等简单概念,并在自己的程序中应用了工厂模式。我没有完成的部分是对于表达式化简的方法。在我原本的设想中,我的程序应当要模仿我进行化简,即在自己已有的知识中匹配,看看能不能找到相应的化简,能就进行化简,否则放弃。这个过程可以分为多个步骤:从看到表达式转化为大脑中的概念,这个过程我的程序已经实现了;比对已有知识,这就需要两个方面,一个是强大的知识库,另一个是精准的检索方法。当然我设计的时候没有那么大的能耐,去建立一个强大的知识库(现在想想,可以就把这个知识库设置成为一个类,里面没有任何数据,纯粹是一堆化简方法的集合),同时使用高效的检索方法(我认为如果真的要实现的话这才是难点)不过看起来我好像是白费心机了,三次作业只要AC的点就没有下过99分的。

          第一单元的训练让我对于面向对象有了一个初步的、感性的体验,当然,就这么一点训练量是不足以支持我对于面向对象的深入思考的。我只是初步地、还不是很熟练地使用继承、多态等简单的方法罢了。

      第二单元:多线程、线程安全

          第二单元的任务是设计电梯。为了让所有的乘客都能到达目的地,电梯必须合理安排自己的行程。在这一次的作业里,面向对象的思想得到了进一步的加强,因为有了现实基础的支撑。作为一部电梯,我是不会管里面的人想去哪里的,我只负责依照按钮给我的指令按照自己的调度算法移动并向外发送信息,因此,电梯被抽象成为一个对象,只管做自己的事。本来,我的想法是把人也作为线程看待,但是后来发现这样实现起来比较困难,于是就放弃了,此时,电梯也从携带者变成了绑架者,因为人没有任何活动,电梯必须帮助他们完成活动。在这个过程中我出过一次线程安全的bug,那就是死锁。我一直没有注意到电梯和中控器有一个相互调用的关系,然后就卡住了。

          第二单元整体的架构安排是,初始的工作对象在主类中生成完毕后投入工作,这些工作对象包括多个电梯线程、一个托盘以及一个具有二重身份的生产者。具体就是,生产者通过获取指令来通过外部电梯类型库类来具体安排乘客的行程,然后放入托盘让电梯抢人。之所以说生产者具有二重身份,是因为生产者在生产完毕后立刻开启监控者模式,监控各个电梯的状态,一旦发现所有电梯都在休眠则认为运行结束(其实这个并不安全,因为乘客会下电梯,有可能最后一个乘客下电梯后还没上电梯,此时所有电梯休眠,如果监控者被启动就会误认为结束,因此我设置了1次/s检查40次的方式,只有每次都发现符合条件才启动退出程序)现在想想看生产者线程完全可以和监控分离,这样也更符合OO的思想。

          第二单元的痛苦训练让我明白了多线程的基本运作方式,也了解了死锁(在同学们的分享和操作系统课程有了对于死锁的产生和避免有了更深入的了解)同时,在这个单元,面向对象的方法成为了基础,而我也能做到得心应手地运用。

      第三单元:好处多多的JML

          第三单元主要是JML的训练,从这个单元开始,我们就不怎么关注代码了,开始更多地讲理念上的东西。总体来讲我觉得JML最大的好处在于利于分工合作。对于开发者而言,我只关心我的程序要具体实现的目的而不关心过程,因此我只需要给出JML,让团队其他人员帮我搞定算法即可,比如我不擅长图论,那么我就给一份JML,让其他人实现时间复杂度低的算法。当然,就合作的角度而言,JML其实能做到满足大部分需求,不过我认为还是应该搭配适当的自然语言,目的是帮助代码实现者尽快了解大概的目的,提高合作效率(当然,自然语言可能会给代码实现者错误的印象,破坏JML的作用)JML的另外一个好处在于自动化测评,如果我们能让机器明白自己要干什么,那么我们就有办法让机器自己测评程序。实际上,相关的JML的工具已经存在,但是非常不完善,尚处于发展阶段,因此JML这个方面的作用被极大地压制了,我们没法使用。不过换个角度考虑,这么一片待开发的荒地,谁能上谁就能成为先驱。

          第三单元结束后我基本上已经能熟练地运用面向对象的方法了。

      第四单元:图形化的UML

          UML给人的感觉是适合做大型的项目。“谋定而后动”,UML就是这个“谋”。UML给人直观的图形化模型,相比于JML能让人更容易理解模型,而且UML支持层次化设计,这就意味着其可以做到支持大型的项目而不杂乱无章,相比之下,JML关注的格局就比较小了。

          个人感觉对UML没什么深入的体会,上面说的都是我的一些映象而已。最后一个单元的作业没有要求运用UML,只是要求搭建一个UML解析器,按照要求谁都可以做到,仅此而已。

    三、测试

      个人觉得测试方面有收获,但是没有多大进步。到第四单元,我的测试策略依然是特殊对待,根据程序本身的特点全局测试来找bug,一般来讲,我的测试可以覆盖到基本所有特殊情况,但是在边界测试和压力测试方面还是不行,而且那种数据一多就可能出问题的测试也基本没有构造过。说是有收获是因为掌握了单元测试的方法,通过JUnit来分别测试每个方法,不过个人感觉,这种测试的效率和代码有很强的关联,低耦高聚的代码用JUnit可以成批、高效地测试,反之,若代码耦合度很高,单元测试就需要引入大量的前置条件和后续处理,测试效率自然低下。

      顺便一提,在四个单元的作业中,第二单元的测试是极为特殊的,多线程意味着程序的运行本身会受到断点的影响,因此普通的debug方法在此处是失效的,而且,由于多线程存在的随机性,一个死锁的检测不仅需要合适的代码支撑,更需要十数次乃至上百次重复运行才能找到bug。而且,就我自己的经历来说,在官方测评机上,我试图通过各种输出来确定bug的位置,但是一旦增加了输出,大片的RTLE全部变成了WA,甚至我增加了无效输出也会导致RTLE变成AC。在这个过程中,死锁没有消失,它依然存在,但是很显然,增加的输出改变了虚拟机原有的运行,降低了死锁发生的概率,但是对于其中的机理我们一无所知。总结下来,我认为多线程中,增加输出是个定位bug的好方法,但是同时,如果存在死锁的话,发现死锁所需要的运行次数会改变,可能会提高。

      目前,debug的方式说到底就是两种,一种是直接打断程序的运行,具体查看程序的运行方式(事实上,这也是增加了输出,只是debug的程序帮我们封装了这些过程而已),另一种是在程序中设置线索输出,通过这些线索推测程序运行的场景。显然,第一种方式效率较高,在单线程的模式下是最优的选择,而第二种看似麻烦,但是在多线程背景断点失效的情况下也只能使用它了。

    四、课程收获

      ·有用的知识增加了。经过了一个学习OO课程的打磨,至少我有用的知识变多了。通过第一单元的训练,我掌握了相当的java语法和OO的技巧;通过第二单元的训练,我能让自己的程序跑多线程了;通过第三单元的训练,我能使用和理解JML了,这意味着我可以高效地和别的理解JML的人合作了;通过第四单元的训练,我知道UML了(超心虚)

      ·工程的能力变强了。感觉OO课程和以往课程有一个很大的不同点,那就是超长的代码量,在以往的课程中,每一个任务的代码量至多不会超过500行,然而在这次课程中,每个单元的代码量轻轻松松就破千,有的甚至会达到两千行以上,而作为代码管理者的我,在管理这些代码的时候一点都不吃力。此外,后两个单元的模式都是通过代码发布零散的任务,而我也能很好地完成这些任务,就说明我有和别人合作完成一个项目的能力。

      ·抗压的能力提升了。emm这个方面我不是很想多说,不过OO和其他硬核的课程一样一路走过来本身就是一次对人的压力测试,好在我通过这个测试,而且似乎还取得了不差的成绩。

      ·具有了面向对象的思维方式。我认为,面向对象和面向过程的一大区别在于,整个流程当中到底有多少个主体和客体。在面向过程的思维中,只有一个主体,那就是程序员。程序员必须精密地设计算法的每一个步骤以确保整个程序不出问题,退一步讲,即使程序出了问题,程序员也要做到精确定位问题的所在。如果程序的规模小一点那还好说,如果程序的规模哪怕只是上千恐怕程序员也吃不消。而面向对象的思维中,除了程序员之外,还存在许许多多个对象,这些对象管理着一些数据和方法,虽然依然由程序员定义,但是这些对象往往权责分明,程序员需要把控的是整体的架构,这样,哪怕是哪个地方出了什么差错,程序员也能通过对象之间的关系溯源,最终找到并修复bug。如果将编程比作行军打仗,面向过程就好比是你一个人直接调度整个军队,军队规模小一点还好,大了就吃不消;面向对象则是你安排好了一整套制度,所有的对象各司其职,有序地按照你的命令行事,你的任务是统筹规划,高级将领们负责把控整个战况,班长们根据士兵的特点安排战术。其实,这个比喻中也透露出了OO的两个重要的点:层次化以及抽象。抽象意味着上层的对象不应该也不可以管下属的对象具体干了什么,而层次化则提供了一套清晰的逻辑。但是,如果我们想的深入一些的话,这个比喻也隐含了OO的一个重要的缺点:为了方便管理所付出的代价。所有的对象各司其职,层层调用,那么最终的结果就是,整个程序的存储的位置相当的离散,导致数据的空间局部性下降,直接影响了程序中最致命的存储部分,导致程序运行效率降低。

      ·一些其他的重要的思维方法被激活了。具体来讲是从生活中获得经验的思维方法和从前人获取智慧的思维方法。在第一次作业中,为了优化性能我可谓绞尽脑汁,但是最后从自己的思考中得到启发,虽然最后没有完全成型,但是最终的效果可以确保我能看出来的东西我的程序也能做到;在第二次作业中,我从已有的电梯中得到启发,设置了按钮(其实现在想想,应该做一个按钮系统的),成功分离了电梯的请求和运行。在第三单元的作业中,我苦于不了解各种容器的性能,于是去查看各种容器的底层实现,除了了解的容器的底层实现和性能之外,有不少的意外收获,比如定长数组扩容的设置,以及能位运算绝对不乘除法等等。此外,OO课程还极大提升了我的思维深度,虽然我举不出来什么例子,不过我现在看到事物之间的联系,并从这些联系中得到启示的能力大大增强了。

    五、改进建议

      ·实验课要有具体的反馈。基本上所有的实验课都没有什么反馈,有的话也是下一次实验中的一些总体的情况,没有具体就个人公布测试结果。这种缺少反馈的与其说是实验,更不如说是考试。

      ·提供测评机CPU时间检测。因为本地测试和测评机测试的环境不一样,所以极限测试的结果可能大相径庭,建议在测试的时候提供一个CPU测试入口,可以自己放数据检测。考虑到测评机的测试压力,可以对学生采取分时段+限制次数的方式,比如按学号把学生分组,第一组在每个小时的前10分钟允许使用带CPU时间的测评机,然后每个学生每小时只能测试多少次这样。

      ·减小pre和第一单元的难度差距。可以选择降低第一单元的难度或者增加pre的个数。我注意到pre是我们这一次才加的,但是就我个人的情况和身边同学的情况来看,pre的缓冲效果还是差些,如果不是我为了第一次研讨课受pre启发自己去了解工厂模式,恐怕我的效果也不见得会这么好。

    六、线上OO学习体会

      还行,不利的因素就是在家里惰性大,但是有ddl每周催着也查不到哪里去;有利的因素是研讨课采用腾讯会议的模式天然消除隔阂感,交流起来也更方便。总的来说效果不错,不过也没有比对,不知道和线下比哪个更好┑( ̄Д  ̄)┍

      最后感谢助教们和老师们的用心,我也很开心能和大家一起圆满地结束面向对象的课程!

  • 相关阅读:
    PostThreadMessage
    WaitForSingleObject函数的使用
    CodeWarrior环境下中断使用
    Activity跳转时生命周期跟踪
    win7 VS2012+openCV-2.4.11 配置
    CodeBlocks16.01+wxWidgets3.0.2
    MFC一个类访问另一个类成员对象的成员变量值
    无法打开包括文件:'atlrx.h'的解决办法
    STM32f103的数电采集电路的TIMER定时器的使用与时序控制的程序
    STM32f103的数电采集电路的双ADC的设计与使用
  • 原文地址:https://www.cnblogs.com/zzy122530/p/13158412.html
Copyright © 2011-2022 走看看