zoukankan      html  css  js  c++  java
  • OO第二单元总结

    OO第二单元总结

    ​ 第二单元结束了,比起第一单元对OOP的懵懵懂懂,第二单元才开始真正思考了什么是面向对象,什么是好的架构,什么是好的设计。查了很多资料,看了很多大佬的博客,回顾了前面的课程,重新审视自己本单元的设计。

    分析需求

    ​ 本单元要求模拟调度电梯运行,实现上下楼载客等功能。而电梯的数量和功能也在不断的增加和多样化。从作业1的单部电梯搭载全部乘客并且不限制电梯的载客量,到作业2的多部电梯搭载乘客,每部电梯有最大载客量,再到最后作业3的电梯不仅有最大载客量,还有不同的种类,不同的载客量,不同的上下楼时间,还有增加电梯。这样的增加需求也算是迭代开发一个很好的例子了。

    ​ 然后就分析具体需求,本次主要分两种情况。一个是乘客,一个是电梯。乘客提出请求,表示自己出发楼层和目的楼层,电梯接受请求,处理乘客请求,将乘客从出发地送往目的地。因此我们需要接受乘客请求,处理乘客请求。

    ​ 具体到每个作业。作业1需要接受乘客请求,分派乘客,此处分派乘客就是需要送往一个电梯。作业2在作业1的情况下,需要决定分配乘客到哪个电梯。作业3在作业2的基础上还要决定乘客是否需要换乘,增加电梯后乘客整体情况需要这么变。

    设计阶段

    作业1

    ​ 本次作业中,先设计一个输入线程,用于实时接受乘客请求。因为只有一个电梯,本次作业并未设置单独的调度器,输入线程将请求放入请求队列,电梯直接从请求队列中拿出请求。单个电梯采用LOOK算法。

    ​ 本次作业较简单,架构并不复杂。主要采用了生产者-消费者模式。

    UML类图如下:

    1

    作业2

    ​ 本次作业引入了多部电梯。在助教的点拨下,我加入了调度器,通过调度器分派乘客请求给各个电梯,各个电梯对自己单独的请求队列处理,每个电梯交互较小,耦合度较小。同时,为了实现性能的优化,第2次作业电梯我也采用了换乘模式,需要换乘时,电梯再将乘客请求放回请求队列,因此电梯从第一次作业的纯碎的消费者变成生产者和消费者。同时requestScheduler类借鉴了Worker-Thread模式。

    ​ 老师课上讲到,不要在一个线程中直接引用另一个线程,但我在Scheduler线程直接引用了Elevator类,不是一个良好的设计(应该对每个电梯加一个请求队列的类,实现调度器和电梯的交互)。

    ​ 总的来说,第一次作业到第二次作业架构变化并不大,即是因为第一次作业预留了一些迭代开发的接口,更多的是作业跨度并不大。

    UML类图如下:

    2

    作业3

    ​ 本次作业较之前还是有较大区别,主要区别还是体现在增加电梯和电梯停靠楼层太混乱。但在第二次作业中,我已经涉及到了换乘问题,因此第三次作业需要改动的只有换乘策略和选择电梯的策略。

    UML类图如下:

    时序图如下:

    实现阶段

    ​ 本单元中,最重要的是线程问题。既要避免轮询,又要保证不死锁,及时唤醒和结束线程。

    ​ 为了便于修改request,将request信息复制到新建的Person类。

    ​ 第二次作业的分派主要是按照楼层分派,将电梯限制到连续楼层,然后调度器分派即按照楼层分派即可。

    ​ 第三次作业分派先看是否是只有特殊楼层(只有一种电梯能运),还是换乘站,换乘站根据谁请求队列内人少,符合电梯运行规则。

    ​ 但第三次作业由于电梯换乘比较麻烦,因此许多特殊楼层需要特殊判断,为了实现更好的性能,Scheduler类需要了解一些Elevator的信息,因此耦合度较高。

    ​ 同时在三次作业中,特别是第三次作业,Scheduler类越来越长,甚至超过了checkstyle的限制,因此又加了Choice类,来帮助实现选择。

    设计分析

    SOLID设计原则

    SRP原则

    每个类或方法都只有一个明确的职责

    本次作业中,每个类都有自己明确的任务,但部分类承担的任务过重,还有待改善。

    OCP原则

    无需修改已有实现(close),而是通过扩展来增加新功能(open)

    在前面两次作业中,实现的较好,这也使得迭代拓展变得较容易,但第三次作业中,为了追求性能,导致耦合度变高了许多,拓展也变得难了许多。

    LSP原则

    任何父类出现的地方都可以使用子类来代替,并不会导致使用相应类的 程序出现错误。

    只有在第三次作业中涉及到电梯继承,由于每个电梯行为差异较大,父类很少有实际的有用的行为。

    ISP原则

    任何父类出现的地方都可以使用子类来代替,并不会导致使用相应类的 程序出现错误。

    在电梯类中,我是用的继承,但仔细想想,不同电梯其实具体行为差异较大,用接口来抽象层次比继承感觉要好一些。

    DIP原则

    高层模块不应该依赖于底层模块,两者都应该依赖于其抽象

    本单元作业中层次化设计较少,更多的只是数据交换,以后应该将一些行为抽象化为层次,来到达更好的架构。

    测试阶段与Bug

    ​ 本次作业出现的Bug较少。本次作业可能出现的bug包括,电梯运行行为出错(包括开关门,上下客等),死锁,轮询,电梯无法及时唤醒和停止,换乘时未将乘客送往目的地等,总的来说本次作业的bug除了多线程不稳定外,还是比较好避免的。

    ​ 前两次作业电梯行为比较死板,主要集中测试特殊情况,比如单层楼上许多人的情况,这种情况也在互测时hack到了人。

    ​ 第三次作业更多的是集中特殊楼层的起始与到达,比如换乘站1楼,15楼,还有及其特殊的3楼等,这也让我在互测中hack到了人。

    自我反思

    ​ 本单元我对自己的设计并不是很满意,不仅是对自己电梯的实现和性能,更多的是架构和层次设计感觉没有让自己满意。经过了这么久的学习,让我对什么是好的代码,设计,架构有了新的理解。

    ​ 从单行代码讲起,要实现让人易懂,包括代码的规范和逻辑的复杂度。尽量少枚举数字等特征,转而用更抽象的行为描述,每行代码要清爽整洁,这样会对迭代开发,或者合作开发时带来巨大帮助。而我这次虽然较一单元更注重了这方面,但仍做的不够理想。

    ​ 然后是方法和类,方法要尽量做到简洁,不要变成“面条代码”,更多地以抽象层次描述。类,也是,避免出现God类和Idiot类,一个类不能太过复杂,不能太过简单,类之间应该有明确的分工,这样才能更好的实现高内聚低耦合思想。前两次作业还做的较好,为了追求性能,第三次作业就开始类之间耦合度明显上升。不是一个很好的设计。

    ​ 最后是架构,一个良好的设计是要兼顾性能和正确性的。但有时候由于自己水平有限,很难两者兼顾,很难在追求性能时保证各个类之间的耦合度很低。虽然看起来对于某次作业或者工程来讲,性能高可能是更好的(毕竟能得更多的分),但是长久来看,为了开发一个更大的项目,为了迭代开发,追求性能带来的耦合度的上升很难满足OCP设计原则,维护代码难度大大上升,也就是很难在不修改或者修改很少代码的情况下实现迭代开发,必须要修改大量代码甚至是重构的情况下实现,如果项目较小还有可能实现,但工程一大,这样几乎是天方夜谭。同时,我的架构中也缺少抽象层次,更多的是“你干什么,就给你分派这个工作”,工作一多,管理自然就很困难。如果能将行为抽象出来,相近的行为统一管理,既好维护,也为迭代开发提供了很好的基础。

    ​ 因此我尽量在以后的作业或者其他场合开发代码,要做到从开始花尽可能多的时间去思考架构,不要认为最开始作业简单,就随便采用一个“只是能用的架构”,导致耦合度较高,迭代开发困难;同时也应该将行为层次化,将数据层次化,将结构层次化。(感觉说的有点空,只能慢慢感受了。。。)

    最后,感谢课程组的老师助教们的付出,感谢大佬们的分享。希望自己在下个单元能学习老师和大佬们的意见,真正地实现一个好的架构。

  • 相关阅读:
    Asp.Net Web API 2第八课——Web API 2中的属性路由
    Asp.Net Web API 2第七课——Web API异常处理
    Asp.Net Web API 2第六课——Web API路由和动作选择
    Asp.Net Web API 2第五课——Web API路由
    开始学习python
    BMI 小程序 购物车
    深浅copy 文件操作
    字典 dict 集合set
    基本数据类型 (str,int,bool,tuple,)
    python 运算符
  • 原文地址:https://www.cnblogs.com/Skypowerr/p/12719221.html
Copyright © 2011-2022 走看看