代码改变世界

面向对象项目二总结感想

2019-04-24 21:36  16231108  阅读(284)  评论(0编辑  收藏  举报

一.设计策略及自我分析

  这三次作业我的思路还是比较顺畅的,从第一次到第三次只是做了一些功能扩展,而没有像之前一样重构代码,所以接下来我将三次作业作为一个整体介绍。

  我的思路是开辟了三类线程:主线程、调度器线程、电梯线程。主线程中维护一些共享的对象(请求列表),调度器从标准输入读取请求并添加进请求列表,电梯从请求列表获取请求并按内置的调度算法将乘客送到目的地。

  老师曾建议用调度器宏观调度所有请求,电梯只是接受调度器的命令然后执行停靠操作,可是我觉得这样一来调度器算法太过复杂,反而是让电梯自己去查看请求、决定停靠较为简单,所以我在电梯内部实现了调度算法,电梯获取全局的乘坐电梯请求列表并结合自己内部乘客的下电梯请求列表决定运行到哪一层停靠,这样每个电梯都有一个近似最优的调度算法,多部电梯合作时可能不是全局最优,但性能也不会太差。

  具体来说,我一共用到了十一个类(三次作业都是这十一个类),其中EleReq是个内部类:

 

  Elevator类就是电梯(线程),它有最大载客量、运行状态(上下)、类型(ABC)、可停靠楼层、运行速度、出电梯请求(一个列表,在哪些楼层有人会下电梯)等属性,也有一个run方法,每到一层,电梯就下乘客、上乘客,然后结合外部全局的入电梯请求列表和自己内部的出电梯列表判断自己下一步要到哪个楼层,同时为了提高性能,会在它路过的楼层也做个判断有没有新人要在这层搭电梯的如果有就开门否则不停留。如果电梯发现没有任务要执行,就会wait()挂起等待唤醒。

  OutReqList就是电梯内部的出电梯请求列表。

  Flag是个标志,如果monitor发现读取请求完毕,就改变这个Flag,这样电梯知道不会有人来了就可以停止运行而不是死等200秒结束线程。

  Person类是我对PersonRequest类进行了包装,目的下面会仔细讲。

  FloorReq就是一个集合,将在相同层请求进入电梯的人放在同一个集合里,在相同层请求出电梯的人放在一个集合里。其实OutReqList和InReqList就是FloorReq的数组。

  InReqList是全局可见的入电梯请求列表。

  Reflact是映射数组,由于电梯层数有负有正不方便,我把层数从-3……20层映射到了1……23层(第0层作为特殊状态,比如电梯发现所有的请求列表都空了,就将自己的nextfloor置0)。

  Monitor读取请求并放到InReqList,同时通知(waiting的)电梯有新任务到了要起来工作了。

  然后特殊讲一下Person、PerReq、EleReq三个类,因为考虑到乘客可能换乘,只用一对from、to不是很方便,应该有一个请求数组,第一次从搭A电梯from到middle,第二次搭B电梯从middle到to这种,PerReq就是这个数组,而数组里的每个元素就是EleReq。

  最后说一下第三次作业,第三次作业我每种电梯都会对应一个InReqList,这样一来我其实就有ABC三个InReqList,每种电梯只负责完成对应类的InReqList调度,对其他两个不做响应。每个乘客到来时我会按照随机算法给他一条最优的(可能换乘)路径放入PerReq,然后看看他PerReq的第一站是要搭哪类电梯,然后将他放入那个InReqList,电梯将人送到后删除PerReq第一项,看看第二项是不是null,如果不是,将第二站请求送到相应InReqList,如果是,说明人已经成功送达目的地。

二.代码分析

时序图:

  觉自己的代码还是勉强算得上高内聚低耦合的,定义了较多的类,类的功能也比较单一。

三.关于Debug:

  多线程Debug还是较为费劲,不好跟踪,只能自己printf一点点缩小范围,而且前期一定要设计好,先自己想明白要让自己的线程怎么合作,不要贸然开始编码。我遇到的bug还是比较少的,这应该是仔细构思的功劳。

四.关于测试:

  多线程的Bug有随机性,所以要注意同一个样例也可能时对时错,不可放过,另外把握好边界条件,比如电梯送完人停在了10层,这时十层来了个人,电梯能不能当即打开门,还是先傻傻地上(下)一层再回来?再比如两电梯同时来了5层,5层那位乘客会不会不幸地被一分为二或者两部电梯都上不去?不过因为多线程不好控制时间,所以与其这样精心设计边界测试,不如仔细考虑所有可能的边界情况然后从逻辑的角度检查自己的代码能否同步正确。

五.心得体会:

  线程安全:最简单的方法,将所有线程间要共享的对象都加上锁,这样性能一定不理想;想要更高的性能,一方面可以考虑细化共享对象,比如全局的InreqList是共享的,但我每次读写它的时候都要共享整个列表吗,其实不是,每次读写只会改变某一层的请求集合,所以只要synchronized(InReqList[i])(i是层数)就可以了,这样其他线程访问InReqList[j](j!=i)就可以并行了;另一方面可以考虑是否有必要同步,比如读读间其实不同步也没问题,再比如有时是在循环里检查共享对象(读),这时如果有写入,读者迟早会感知到这个变化,这样读写也不需要共享了。

  设计原则:SOLID (单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期 引入的记忆术首字母缩略字,指代了面向对象编程和面向对象设计的五个基本原则。

  单一责任原则(Single Responsibility Principle )可以看作是低耦合、高内聚在面向对象原则上的引申,将责任定义为引起变化的原因,以提高内聚性来减少引起变化的原因。责任过多,可能引起它变化的原因就越多,这将导致责任依赖,相互之间就产生影响,从而极大的损伤其内聚性和耦合度。单一责任,通常意味着单一的功能,因此不要为一个模块实 现过多的功能点,以保证实体只有一个引起它变化的原因。具体到这次作业,我觉得自己的电梯实现的较为臃肿,不过也较为独立,只依赖外部的InReqList以及一个停止和启动的消息。

  开闭原则(Open Close Principle ) 认为“软件体应该是对于扩展开放的,但是对于修改封闭的”的概念。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。这一点我做的不够好,只是保证了在大的框架不会重构,不过有些地方还是要修改代码。

  里氏替换原则(Liskov Substitution Principle) :里氏替换原则 认为“程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的”的概念。此次没有用到继承,不过我觉得自己的三类电梯本来可以有一个公共父类的。

  接口隔离原则(Interface Segregation Principle) :接口隔离原则 认为“多个特定客户端接口要好于一个宽泛用途的接口”的概念。 本次未自行实现接口,不过runnable接口的确很强大。

  依赖反转原则(Dependency Inversion Principle): 依赖反转原则 认为一个方法应该遵从“依赖于抽象而不是一个实例” 的概念。依赖注入是该原则的一种实现方式。类可能依赖于其他类来执行其工作。但是,它们不应当依赖于该类的特定具体实现,而应当是它的抽象。这启示我在设计的时候要从上到下、宏观到微观地设计。

设计原则部分参考于:

http://www.cnblogs.com/huangenai/p/6219475.html