OO第二单元总结

第一次作业:

1.设计策略

  程序采用生产者-消费者模式,一共有两个线程,一个是主线程,主要负责输入;另一个是电梯线程,用来处理请求。此外,程序还有一个核心——调度器,内置请求队列和调度函数,相当于托盘。主线程不断将请求输入到调度器中,而电梯线程每完成“上楼-开门-出人-进人-关门”的一次循环就从调用一次调度器的调度函数,以获得下一步的动作。如果调度器中的队列为空,则wait(),直到被唤醒。

  调度器采用的是look算法,电梯的运行方向一般不改变,除非在该方向上已经没有请求。而调度函数的作用就是在电梯运行方向上寻找需要最近停靠的楼层,并将相关的出入乘客的信息包装起来传递给电梯线程。在这个过程中调度器需要获得电梯的楼层的状态,所以两者采用观察者模式,调度器为观察者,电梯为被观察者。

2.程序结构

类图:

代码行数:

第二次作业:

1.设计策略

  第二次作业只需在第一次作业上做一些迭代,主要的修改在线程的创建和需求的分配上。在线程的创建部分只需要将原先的静态创建单部电梯修改为动态创建多部电梯,同时为每个电梯配备一个调度器。而需求的分配方式有很多,我选择了一种比较简单的方式,即把新输入的需求放入队列长度最短的调度器中。

  对于电梯容量限制的问题,只需稍稍修改一下调度器中的调度函数,在寻找楼层时考虑电梯的容量,出人量与进人量的差不能超过电梯的剩余容量,同时当电梯人数满时跳过进人请求,只考虑出人请求。

2.程序结构

类图:

代码行数:

第三次作业:

1.设计策略

  第三次作业同样也只需要在第二次作业上做一些迭代。最主要的修改是增加了主调度器,负责对各个电梯的调度器进行控制。主调度器的核心部分是对乘客请求进行处理,判断是否换乘,如果不换乘需要哪种电梯,如果换乘则需要哪两种电梯配合。同时主调度器也内置请求队列,可以接收主线程的乘客请求,同时也可以接收来自电梯的需要换乘的乘客请求,然后将队列中的请求送入对应电梯的调度器中。

2.程序结构

类图:

代码行数:

可扩展性分析:

SRP——单一职责原则

  总体而言,每一个类的职责都是比较明确单一的。主类负责相关输入,调度器负责电梯运行策略,电梯负责相关输出。但是在第三次作业中,主类不仅负责输入,同时也负责电梯的初始化及后续的增加电梯请求的处理,这一点违反了SRP。

OCP——开闭原则

  OCP主要体现在作业的迭代过程。在第一次作业到第二次作业的迭代中,为调度器中的队列类添加新的限制人数的方法,即达到电梯有限容量的效果。但在第二次作业到第三次作业的迭代中,由于一些请求需要换乘电梯,原先调度器中的队列类的数据存储方式不能再满足需要,必须修改相关的域和方法,这点在设计时有些欠缺。

LSP——里氏替换原则 & ISP——接口隔离原则

  本设计中的主要部分未用到继承和接口,而是为电梯的构造方法传入不同的构造参数从而构造不同类别的电梯。从这点来看设计的抽象层次不够,如果电梯的属性发生改变就可能需要重构代码。

DIP——依赖倒置原则

  在这一方面仍是抽象层次不够的问题,应当为电梯、调度器都创建一种接口或抽象类,然后各类电梯和调度器都实现该接口或继承自该抽象类,增加程序的可扩展性。

bug分析:

  第一次作业并没有出现bug,但第二次和第三次作业都出现线程无法顺利结束的bug。在第二次作业中导致该bug的原因主要是在输入结束后,如果有电梯线程因队列为空而阻塞,就没有其他线程能够唤醒该电梯线程。而第三次作业是当电梯已满且处于顶楼或底楼时,调度器的调度函数没有改变电梯方向,于是进入死循环。

感想:

  经过了第二单元的学习,我对线程安全,线程同步的概念有了一定程度的理解,也初步体会到多线程编程的不确定性所带来的debug的困难。此外,我也认识了观察者模式,生产者-消费者模式等设计模式,体会到了优良的设计模式所带来的优势。

  对于这三次作业,我对自己的设计还比较满意,两次迭代都只在前一次作业的基础上进行功能的扩展,但最终的成绩却不太理想,其主要原因还是在评测方面下的功夫太少,在后续的学习中还需继续努力。

posted @ 2020-04-15 15:39  xxlscxx  阅读(134)  评论(0编辑  收藏  举报