博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OO第二次博客作业
阅读量:7094 次
发布时间:2019-06-28

本文共 5341 字,大约阅读时间需要 17 分钟。

OO Unit2 总结

​ 第二单元的作业是面向多线程的电梯编程,通过这一单元的作业,笔者基本掌握了多线程编程的方法。

第五次作业

  • task:单部先来先服务调度电梯,用户请求可以在任意时刻到达。

  • 类图

    1616603-20190419225837744-1534021832.png

    时序图

    1616603-20190419225845850-938582725.png

    设计策略

    虽然第一次作业仅仅只有一部电梯,但是需要一个专门的线程用于处理输入,与电梯线程并发执行。由于需要准备向多电梯扩展,我在这次作业设计时并没有直接将输入给到电梯,而是经过了scheduler中转后,再由电梯获得然后执行。

    这次作业我总共设计了两个线程,InputHandler和Elevator,共享对象是Scheduler。InputHandler将处理好的请求放入Scheduler的队列,ELevator将请求从Scheduler中取出然后执行。

    因此为了保证线程安全,需要对Scheduler的requests队列的操作做同步控制,具体体现在InputHandler向requests队列放入请求和Elevator从requests队列取请求时都需要先获得requests队列的锁。

    至于两个线程之间的协同体现在1.requests队列为空时,电梯需要等待,我采用的是CPU轮询的方法。2.InputHandler线程结束后,Elevator线程需要知晓,w为此我在InputHandler中设置了一个hasInputFlag的变量,标志是否还有输出,电梯可以通过查询该标志变量来检查输入线程是否结束,以便决定自身线程是否结束。

    优点

    • 输入处理和电梯模拟可以并发执行,程序运行效率高。
    • 电梯的运行逻辑分离程度较高,每一个小步骤都写成了一个方法,然后用一些顶层方法包起来,主控制流只需要顺序调用这些顶层方法就可以了。

    缺点

    • CPU轮询过于消耗CPU时间。
    • Scheduler并没有单独作为一个线程,可扩展性不够。
    • 电梯属性硬编码,可扩展性不够。
  • OO度量分析 -- 类

    1616603-20190419225852780-951105938.png

    • LOC:类代码行数。
    • OCavg:平均方法复杂度。
    • WMC:带权方法复杂度。
    • NAAC:类属性个数。
    • NOAC:类方法个数。
  • OO度量分析 -- 方法

    1616603-20190419225855939-1786109461.png

    • CONTROL:控制语句数。
    • LOC:方法代码行数。
    • ev(G):核心圈复杂度。
    • iv(G):方法设计复杂度。
    • v(G):圈复杂度。
  • 设计原则检查

    • Single Responsibility Principle:每个方法基本上都只负责一项功能的执行,符合单一职责原则。
    • Open Close Principle:由于存在硬编码等问题,可扩展性不高,对后续需求增加不利。
    • Liscov Substitution Principle:不存在子类的设计,满足里氏替换原则。
    • Interface Segregation Principle:不存在接口设计,满足接口分离原则。
    • Dependency Inversion Principle:没有考虑将楼层也抽象为对象,只是简单进行了楼层数的记录。

第六次作业

  • task:单部可捎带电梯实现,用户请求可随时到达。

  • 类图

    1616603-20190419230003335-1329070056.png

    时序图

    1616603-20190419230009922-538629864.png

    设计策略

    本次设计较第一次有了较大的改变。由于调度器现在需要使用可捎带算法来为电梯分配请求,考虑到程序运行效率问题,调度器也可以作为一个单独线程与InputHandler和Elevator线程并发执行。

    这次作业笔者总共设计了三个线程:InputHandler、Elevator和Scheduler。InputHandler与Scheduler的共享区包括requests队列和inputOver标志;Scheduler与Elevator的共享区包括requests队列和runOver标志。

    为了保证线程安全,InputHandler对requests队列的放入操作与Scheduler对requests队列的取出操作都需要先申请锁;Scheduler将请求放入电梯的requests队列和电梯从自己的requests队列中取请求都需要先申请锁。

    本次作业线程间的协作体现在:首先由InputHandler接收输入进行处理后放入Scheduler请求队列;然后Scheduler执行调度算法,将请求分配给电梯;最后电梯执行运行算法,完成请求。为了实现三个线程间的同步控制,每获得一个输入后,InputHandler对Scheduler进行notify;当requests队列为空时,Scheduler处于wait状态;Scheduler每分配一个请求后,对Elevator进行notify;Elevator的requests队列为空时,Elevator处于wait状态。这样既可以保证不浪费CPU时间,又能够保证各个线程及时被唤醒工作。

    优点

    • 对楼层进行了建模,提高了可扩展性。
    • 将电梯的运行参数包装为runPara、runState两个属性类,即防止了硬编码提高了可扩展性,又不会让电梯的逻辑显得十分冗余。
    • 不同类分工明确,相互之间只通过共享对象进行交互,耦合度很小。
    • 电梯设置了自己的请求队列,便于向多电梯的情况扩展。
    • 使用注册的方式,使得调度器可以知道电梯的信息。

    缺点

    • 电梯类的方法数过多,逻辑过于庞大。
  • OO度量分析 -- 类

    1616603-20190419230022731-1464690753.png

    • LOC:类代码行数。
    • OCavg:平均方法复杂度。
    • WMC:带权方法复杂度。可以看到电梯类的方法的权值很大,表明其方法的调用次数和频率都很高。
    • NAAC:类属性个数。
    • NOAC:类方法个数。
  • OO度量分析 -- 方法

    1616603-20190419230028512-197735980.png

    1616603-20190419230033500-1838010828.png

    • CONTROL:控制语句数。
    • LOC:方法代码行数。
    • ev(G):核心圈复杂度。
    • iv(G):方法设计复杂度。
    • v(G):圈复杂度。
  • 设计原则检查

    • Single Responsibility Principle:每个方法基本上都只负责一项功能的执行;InputHandler和Scheduler类的职责也较为简单,但是Elevator类的功能过于复杂。
    • Open Close Principle:电梯运行参数、楼层都实现了建模,可方便的扩展和修改;调度器分配框架搭建完善,如果有调度算法上的改变,只需要重写调度函数即可。总的来说,符合开闭原则。
    • Liscov Substitution Principle:不存在子类的设计,满足里氏替换原则。
    • Interface Segregation Principle:不存在接口设计,满足接口分离原则。
    • Dependency Inversion Principle:电梯内部其实可以进行进一步的抽象,比如将上行和下行的逻辑单独分离为一个运行器的类,这样也可以降低电梯类的复杂度。

第七次作业

  • task:三部运行参数不同的电梯,使用可捎带调度算法,用户请求实时到达。

  • 类图

    1616603-20190419230147784-1671101675.png

    时序图

    1616603-20190419230151987-1951519519.png

    设计策略

    本次作为是多部电梯的运行,而且每部电梯的属性有所不同。三部电梯之间是并发执行的。

    这次作业笔者总共设计了5个线程:InputHandler、Elevator和Scheduler。InputHandler与Scheduler的共享区包括requests队列和inputOver标志;Scheduler与Elevator的共享区包括每部电梯的requests队列和runOver标志。

    为了保证线程安全,InputHandler对requests队列的放入操作与Scheduler对requests队列的取出操作都需要先申请锁;Scheduler将请求放入电梯的requests队列和电梯从自己的requests队列中取请求都需要先申请锁。

    本次作业线程间的协作体现在:首先由InputHandler接收输入进行处理后放入Scheduler请求队列;然后Scheduler执行调度算法,将请求分配给电梯;最后电梯执行运行算法,完成请求。为了实现5个线程间的同步控制,每获得一个输入后,InputHandler对Scheduler进行notify;当requests队列为空时,Scheduler处于wait状态;Scheduler分配完当前队列中所有请求后,对所有的Elevator进行notify;Elevator的requests队列为空时,Elevator处于wait状态。

    这次作业还新增了换乘的功能,为了实现换乘,笔者的策略是在Scheduler中就首先拆分好,第一段交由电梯执行,第二段暂存在Scheduler的transfer队列中,当第一段被电梯执行完毕后,电梯通知调度器对第二段进行分配,这样就保证了第一段的永远先于第二段执行,实现了同步控制。并且由于transfer队列的存在,Scheduler线程结束的条件不仅是输入结束和requests队列为空,还要算上transfer对列也为空才能结束线程,当所有请求分配完毕后,若transfer队列不空,Scheduler会进入wait状态,等待电梯通知。

    优点

    • 架构清楚,各模块功能界限清晰。
    • 进一步封装了Floor类,设计了Floors类,并且封装了Floor类的所有方法。
    • runState类提供了如up、down等方法,方便对电梯运行状态进行设置。
    • 电梯与调度器的交互通过addToFloor和awakeTransfer这两个方法完成,防止外部类直接对本类的属性进行改变。
    • 电梯调度改用了扫描算法,运行效率提高。

    缺点

    • 电梯类的逻辑过于庞大。
  • OO度量分析 -- 类

    1616603-20190419230200004-174185978.png

    • LOC:类代码行数。
    • OCavg:平均方法复杂度。调度器类中有分配算法的方法逻辑过大,因此导致平均方法复杂度提高。
    • WMC:带权方法复杂度。电梯和调度器的方法的权重都很高。
    • NAAC:类属性个数。电梯相对于第二次作业新增了一些属性。
    • NOAC:类方法个数。
  • OO度量分析 -- 方法

    1616603-20190419230203052-2112091727.png

    1616603-20190419230205550-1501168673.png
    1616603-20190419230209851-1859192073.png

    • CONTROL:控制语句数。每个方法的控制语句数都较少。
    • LOC:方法代码行数。
    • ev(G):核心圈复杂度。
    • iv(G):方法设计复杂度。
    • v(G):圈复杂度。调度器用于进行拆分请求的方法的复杂度略高。
  • 设计原则检查

    • Single Responsibility Principle:Elevator类的职责功能过多。
    • Open Close Principle:在第二次作业的基础上,新增了一些方法来满足新的需求;对电梯运行方法、调度器分配方法进行了修改,但是大体上是符合开闭原则的。
    • Liscov Substitution Principle:不存在子类的设计,满足里氏替换原则。
    • Interface Segregation Principle:不存在接口设计,满足接口分离原则。
    • Dependency Inversion Principle:电梯的抽象层次仍然不够,upFloor和downFloor的逻辑可以独立为类。

BUG分析

自己的BUG

​ 前三次作业在公测中都没有出现bug,由于未参加互测,因此没有进一步发现bug。

测试的方法

  • 静态检查:在重要的逻辑部分对代码做静态检查,比如调度器分配逻辑、电梯运行咯就。
  • 单元测试:在代码编写过程中没写完一小部分独立性较高的方法就对其进行单元测试,保证无误后才进行之后的编写。
  • 综合测试:针对问题的规则,人为制造一定的复杂样例进行测试。对于换乘这种极容易出错的地方,进行重点的测试。
  • 回归测试:三次作业,每一次作业的测试用例都向后兼容,记录下前两次的用例用于第三次作业,节省了部分时间。

心得体会

多线程编程最重要的一点就在于线程安全的处理,凡是涉及到了线程共享的部分都需要进行加锁操作。这里就需要我们找到到底哪些地方出现了共享,通常共享出现在参数传递、返回值、引用变量赋值这些地方,对于多线程编程中的这些场景我们一定要格外注意,对于出现了共享对象,要追踪其所有使用位置,检查操作是否安全,否则一旦出现不可控的安全问题,靠debug来查找问题来源是极为困难的。

除了多线程编程外,这一单元笔者同时还接触到了设计原则。SOLID五大经典原则可以说是在实际开发过程中应该随时恪守的,从需求分析到架构设计到代码实现,每一步都要注意设计原则,而且在实现完成后还要进行设计原则的检查,在不当的地方进行重构以符合设计原则。很多同学可能很讨厌这种工作,认为又麻烦有没有意义,但是笔者认为在实际的软件开发过程中,遵守设计原则是极其重要的。现在由于作业规模小,而且迭代开发次数少,不遵循设计原则看起来影响不大,一旦到了社会上,软件的开发往往规模庞大而且要经过很多次的迭代开发。在这种情况下,如果没有一个统一的设计规范,一个人一套规则,那相互之间的合作就会变得无比困难,别人开发的软件你可能完全没法读懂,更别谈进行迭代开发;即使你能够读懂,如果该产品经过多次迭代后,有些类的安全性、独立性等都已经发生了改变,那么要想在已有基础上进行修改,就可能需要向后追溯到很久以前实现的代码上去进行阅读,白白浪费大量时间。

总的来说,设计原则应该贯穿于整个软件开发的过程,遵守设计原则,可以帮助我们更好的进行设计和开发。

转载于:https://www.cnblogs.com/wpy16131034/p/10739394.html

你可能感兴趣的文章
bzoj4025 二分图
查看>>
文档加密、解密jar包
查看>>
Java 8 字符串日期排序
查看>>
了解Python
查看>>
Java遇见HTML——JSP篇之JSP基础语法
查看>>
a common method to rotate the image
查看>>
测试计划
查看>>
深拷贝与浅拷贝
查看>>
textarea禁止拖动 滚动条隐藏
查看>>
Java下利用Jackson进行JSON解析和序列化
查看>>
Js用正则表达式验证字符串
查看>>
大疆农业专家在线空开课直播课件知识
查看>>
怎样快速搜索自己所需的资料?(90%的人不会使用此方法)[转]
查看>>
POJ_2411_Mondriaan's Dream_状态压缩dp
查看>>
694. Number of Distinct Islands - Medium
查看>>
vue打包后出现的.map文件
查看>>
前端应用框架(三)
查看>>
多线程的死锁
查看>>
定时任务框架Quartz-(一)Quartz入门与Demo搭建
查看>>
css导航栏
查看>>