OO2019第二次作业总结

第一次电梯作业分析

 

 

 

这次作业我采用了两个线程,一个线程(主线程)用于处理输入,另外一个是电梯线程。

在设计上:(1)电梯类从控制器中获得请求,当没有请求的时候wait(),有请求的时候取出一个请求并处理,因为这次作业不涉及捎带,所以直接按模拟电梯运行。(2)控制器类用于从主类接收请求并将请求传递给电梯,控制器要保证线程安全,因为它的成员变量中的请求队列prs和结束标志位idDone都是两个线程间的共享变量。(3)主类读入请求并将请求插入控制器中。

实现上注意用锁保护好控制器类中的共享变量即可。

第二次电梯作业分析

 

 

  

因为这次电梯只是添加了可捎带功能,因此依然是两个线程,主线程处理输入,电梯线程模拟运行。

在设计上这次需要考虑捎带功能的实现。我采用了LOOK算法,控制器将请求分为上行请求和下行请求分别存放在两个队列中,而电梯则比较复杂,维护一个主请求和一个捎带队列,接下来在电梯内实现可捎带的运算过程模拟就和面向过程编程无异了。另外,同上次作业一样,所有的共享变量全部存放于控制器类中,电梯类为只读不可写类,因此在考虑同步控制方面,只需要确保控制器类的线程安全即可。

第三次电梯作业分析

 

这次作业的整体结构其实和前两次差别不是很大,无非是多了一个TranRequest类,用于处理换乘的乘客,以及把sleep和output封装了一下,为了代码看着更整齐一些

 

 

 写到第三次作业代码一共也只有475行,可能是因为我采取的电梯调度策略过于简单。。。

 

 

 

坦白地说,前两次作业比较简单也缺乏趣味性,相比之下第三次作业就有意思很多了。(也可能是我前两次作业写得不认真)。这次因为多了两部电梯于是多了两个线程,就变成了一个处理输入的主线程,三个模拟电梯运行的线程。

在设计上采取尽可能简单的业务逻辑,主要考虑以下几个问题:

(1)换乘。为了尽可能简单,而且考虑到三部电梯停靠楼层的特点,需要换乘的乘客固定只能在1楼或者15楼换乘。这应该是最简单的换乘处理方式,缺点包括:不具有可扩展性,再加一部不停1楼和15楼的电梯就直接崩了;绕路的情况会发生,可能有更近的换乘楼层。不过在某些场景下有可能性能还是不错的,比如在交通量大但不是特别大的时候,将换乘乘客统一在1楼或者15处理可以减少一些停靠次数(强行给自己愚蠢的设计找一些说辞)。

(2)电梯资源分配。很明显三个电梯速度不同,停靠楼层不同,在理想情况下,调度策略也应该有所不同,但对于笔者这样的菜鸡而言,与其自己绞尽脑汁构造尽可能优化的调度策略,不如把问题交给电梯,毕竟这次作业题目叫做智能电梯hhhhh。因此这次作业中,我实现的调度策略大概就是没有调度,控制器只将输入线程传进来的请求进行简单的分类和处理放入请求队列中即可;电梯则从控制器获取请求并模拟运行。

(3)电梯运行策略。上一次我还用了LOOK算法,这次就更简单的直接贪心了。电梯在没人的时候,问控制器离自己最近的乘客,如果没有就wait(),如果有的话,就往该乘客的楼层走,但这时候,这个乘客并没有被"分配"给该电梯,这意味着这个乘客还有可能其他离他更近的电梯接走,如果这个乘客被其他电梯接走的话,那么该电梯寻找下一个目标去接。在电梯有人的时候,就一直运送这些乘客,直到没人,并在期间不断接上同方向可捎带的乘客。当然这样的设计在性能上可能难以得到保障,不过从线程安全的角度考虑,电梯类与前几次作业一样,依然不可写,这样就不需要在电梯中考虑同步互斥的问题了,同样是为了简单hhhh

(4)如何结束程序。电梯进程运行结束的条件是:(1)输入完全部的请求;(2)控制器中没有请求剩余;(3)所有的电梯中都没有乘客。主线程在处理完全部输入后就自行了结了,剩下三个电梯进程在结束条件的判断中相互依赖,因此在某一个电梯线程退出的时候,它需要唤醒另外两个电梯,这是和前两次作业不一样的地方。

接下来考虑SOLID设计原则:

(1)SRP:这几次作业的类虽然不是很多,但每个类的职责都是非常简单明确的,并在每个类的方法中,都尽可能保证每个方法的业务逻辑尽可能简单。

(2)OCP:这三次作业的扩展过程是:单部无捎带电梯—>单部可捎带电梯—>多部可捎带电梯。其实扩展的功能点都是非常合理的,但前两次作业与其做一些拓展性比较好的架构不如直接用naive的方法解决掉,重构成本太过低廉,就会让人忘记重构的痛苦。按照我第二次作业的设计,电梯类的实现中绝大多数代码在三次作业中都是可以重用的,但是我第二次公测测出了bug,就导致我想写一个业务逻辑更简单的电梯。

(3)这次作业我的设计没有设计继承和接口,因此就没有考虑LSP,ISP和DIP原则。当然DIP原则中的dependency不一定是指依赖通过接口产生的抽象,我们可以将方法的pre-condition(方法被调用是需要满足的条件)和post-condition(调用完成后方法的执行结果)作为抽象来建立DIP中所表述的依赖关系,这样有助于我们分析代码。

bug分析

由于高工没有peer review,我就把分析自己程序和分析别人程序的bug都写在这一个部分了。

(1)进程间共享变量。在使用多线程的时候需要各位注意共享变量的保护,当有多个线程可以对同一变量进行读写操作时,要确保这些操作都是互斥的。另外,还需要检查死锁。

(2)电梯中的可变变量。在这次作业中,不可避免的要使用可变变量来描述电梯运行的状态,比如电梯中的主请求和捎带队列,设计者应该清楚每个方法是否会对这些变量进行更新,如果更新的话会产生什么影响等。在完成了设计之后就应该清楚每个类应该有哪些可变变量,以及类中的方法会如何修改这些变量,在实现的过程中应该有比较清晰的思路,更稳妥的做法是把JavaDoc写明白

(3)基本业务逻辑。这部分和普通的测试没有区别,主要通过代码覆盖性检查和构建极端测试样例来进行全面测试。

(4)耦合。其实每个类或者每个方法单独拿出来都比较好分析,但是当它们发生的交互的时候,情况就会变得非常棘手,这也是为什么面向对象设计中要尽可能降低耦合度。这个单元作业中主要涉及的交互在于电梯和控制器,因此需要分析这个两个类互相调用时的业务逻辑,要弄明白有没有潜在修改可变变量的行为,是否发生了更深层次的调用等。

心得与体会

(1)老师上课说,第一单元的作业中很多强测满分的作业还是被测出了很多bug,然后就没有然后了。。。为什么不去深究一下这些深层的bug呢?是意味着这些bug本身没有意义,还是发现更多的设计上的缺陷没有意义?

(2)作为高工高年级的同学表示没有peer review是既幸运又遗憾的,一方面课业压力确实小了很多,但另一方面这样就很面向作业编程了,而且强测出了结果之后就没必要再去纠结更多了,反正全部ac了就说明代码码的还不错,有wa的话下次重构一下就行了,反正也不需要代码修复。

(3)到底什么是你们说的便于实现优化的架构啊?以及优化怎么实现在这样的架构上啊?大佬们怎么实现多层调度的,又怎么搞电梯线程之间相互通信提升效率的?虽然自己想不出来,但蛮好奇的。

(4)当然前面这些问题最好的解决办法是有组织一些老师学生之间的one-on-one talk,有高人帮忙review code自然事半功倍,但基于我国国情基本不可能。之前把优秀作业代码公开的做法其实不错,但是似乎只搞了一次,而且那一次我还在优秀作业中看到了一份纯过程式代码(体谅助教大大都很忙。剩下的部分只有自己多参照优秀代码去review自己的代码,然后不断改进。

(5)代码实现之前的设计是非常重要的,尤其在程序变得复杂之后,将设计考虑清楚之后再去实现就会效率很高,反之则要不断修改之前写过的代码,并且有意思的事情是bug可能不止会出现在代码实现中,设计上也能有bug。

(6)最后关于多线程并发机制,个人总结的经验就是尽可能把逻辑简化,减少共享变量,减少可变变量,剩下的多读多看再加上认真仔细就好了。

最后引用一句刚刚逝世的Joe Armstrong的话给大家共勉(虽然这个人认为OO非常糟糕[1],感兴趣的同学可以去学习一下这位大佬开发的对并发提供良好支持的Erlang语言)

Make it work, then make it beautiful, then if you really, really have to, make it fast.

90% of the time, if you make it beautiful, it will already be fast.

So really, just make it beautiful!

 

[1]http://www.cs.otago.ac.nz/staffpriv/ok/Joe-Hates-OO.htm (希望我不会被封号

 

转载于:https://www.cnblogs.com/huluobo7161/p/10761873.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值