软件工程基础—结对项目—电梯调度

结对项目—电梯调度

作者:北京理工大学 计算机学院 1904班 张驰 魏慧聪

Part1 电梯调度需求分析

1.1 引言

1.1.1 编写目的

本文根据实际电梯调度系统的设计要求,提供了一个整体的架构设计方向,明确了本系统在实际使用过程中的需求

1.1.2 项目设计原则

1.实用性原则:坚持实用性,电梯调度以系统运行效率和乘客最短等待时间为主要目标,采用较为高效的算法,保证系统高效长期运行。

2.灵活性和拓充性原则:系统在满足用户需求的基础上,提供友好的用户操作界面,便于管理调度系统和电梯的运行

3.可靠性原则:系统要足够稳定,发生错误和故障的可能性尽可能减少,所造成的影响尽可能小,对各种可能出现的紧急状况有所对策,保证高容错性

4.标准化和规范化原则:系统规范化和标准化是实现系统的重要原则。采用统一的编码风格,简单的UI设计,保证系统的规范性。

1.2 任务概述

1.2.1 项目目标

本项目的名称是“电梯调度系统”,目标是一个设计一个大厦的电梯调度系统,其中有四部运行电梯可以正常满足乘客上下楼的需求,采用C++语言编写,有独立的运行系统和人性化的UI界面。在设计、编码等过程中还要保证数据的安全性、完整性和准确性。

1.2.2 项目要求

① 电梯调度系统可以正常运行;
② 可以较为高效地满足乘客的需求;
③ 管理界面友好简单,易于操作;
④ 项目具有较高的稳定性和安全性。

1.2.3 项目关键问题

① 电梯调度算法要达到一定效率,乘客等待有忍耐限度,要尽量高效;
② 四部电梯同时运行,需要用到多线程管理,合理梳理各个电梯之间的关系;
③ 定义明确,方便修改电梯和其控制系统的各项参数做调整;
④ 进行足够的测试保证系统的运行正确;
⑤ 实际运行需要考虑实际驱动电梯的组件,采用合适的接口;
⑥ 在人流量过大时控制客运人数,采用合理方案保证电梯安全运行。

程序开发人员需要在规定时间内进行系统设计、程序编码、系统测试、程序调试等任务。

1.3 可行性分析

可行性分析主要聚集在开发可行性、技术可行性上,由于项目并未应用在实际的电梯中,经济可行性和法律可行性分析可以简化。

1.3.1 开发可行性

电梯在现代的高楼建筑中应用广泛,实际上,每栋楼的电梯都会有一个统一的调度系统。一栋楼中的电梯能否顺利运行、运行是否高效、是否安全关乎人们的生活质量,对于提升个人幸福感,便利生活有着非常重要的作用。在实际生活中,已经有相当成熟的电梯调度算法和系统,且时间足够,有一定项目需求,可以进行相应开发。

1.3.2 技术可行性

系统采取QT开发框架进行开发,该框架产生的应用程序可以在任意平台部署,生成本地运行的项目文件。这种可以任意平台部署的特性使得不需要考虑系统版本等部分环境问题,一定程度提高了工作效率。在遵循C++代码规范的前提下,内存也可以得到较好的管理;除此,C++还有运行高效的优点,可以更快响应用户需求。

1.3.3 经济可行性

系统的设计、编码等过程采用免费的个人版QT开发工具,无需购买编译器和服务器,可以在个人电脑上可以正常开发

1.3.4 法律可行性

采用的开发软件是QT开源版,我们承诺,代码均为小组自主创作,将不存在抄袭风险导致的侵权事件。

1.4 系统需求分析

系统需求分析以可行性分析为出发点,本质是回答“系统必须做什么”这个问题,确定系统具备的功能。因此该板块的主要任务是确定系统的功能需求、性能需求、运行需求。

1.4.1 功能需求

用户可以在每一层按下电梯按钮,在电梯内按下按钮选择想要去的楼层。

电梯调度系统需要接收电梯调度系统的调度,在指定楼层停靠接乘客,根据指令移动,并在指定楼层停靠让乘客下电梯。

电梯调度系统需要在用户提出乘坐需求后立即响应,计算出最合适的电梯运载乘客,并发送请求给响应电梯。

1.4.2 性能需求

① 信息完整性:采用值约束,缺省补全等保证信息的完整,当有异常数据产生时,有一定的错误处理能力
② 信息安全性:系统运行过程避免用户可以操控一些不必要的功能,造成电梯运转故障;避免信息传输过程中产生外泄
③ UI管理界面:界面需要简单友好,布局合理,功能完善,对于初级用户容易操作
④ 运行高效性:电梯调度算法和电梯运行算法需要在尽量短的时间内运载乘客

1.4.3 运行需求

系统运行在主流的WINDOWS系统操作平台上,事实上,QT框架的应用程序可以在任意平台部署。遵顼主流的标准和协议以及相应的代码规范,便于系统内部各部分之间交换信息

Part2 PSP表格

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划120150
· Estimate· 估计这个任务需要多少时间6040
Development开发15days
· Analysis· 需求分析 (包括学习新技术)360200
· Design Spec· 生成设计文档120120
· Design Review· 设计复审 (和同事审核设计文档)6060
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)6030
· Design· 具体设计1day480
· Coding· 具体编码4day3day
· Code Review· 代码复审1day1day
· Test· 测试(自我测试,修改代码,提交修改)3001day
Reporting报告1day2days
· Test Report· 测试报告1day1day
· Size Measurement· 计算工作量6060
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划60120
合计23days20hours24days20.5hours

Part3 Information Hiding, Interface Design, Loose Coupling 接口设计

3.1 Information Hiding 信息隐藏

我们结对中决定采用Qt框架的C++语言来实现电梯调度的项目。信息隐藏主要在C++语言中,表现为封装成类的形式。

C++程序开发中实现信息隐藏的三种类型为:不可见不可用,可见不可用,可见可用三种。

我们定义在基于Qt框架的C++程序中,主要通过将数据封装成类的方式来实现信息隐藏。我们在程序中主要定义了三种类:

类名功能
Passenger主要定义乘客,乘客的标识、行为、属性,以及调用电梯调度的接口
ElevatorSystem主要定义电梯调度系统,用于管理4部电梯,用于分配乘客的请求给四部电梯
Elevator主要定义电梯,电梯的各项属性,电梯运行的算法,电梯运行的函数,电梯服务乘客的接口等

3.2 Interface Design 接口设计

接口时不同模块之间或者模块与系统之间的一种通信方式,是模块或系统是痛的约定或者规范。接口必须要保证足够的稳定性以及易用性。在本项目设计的接口中,我与魏慧聪同学对接口设计的要求是:

  • 接口语义明确
  • 接口名称,接口接受参数的类型,接口调用的执行逻辑需要明确
  • 接口的读写权限需要明确

在三个类中,我们设计了乘客类与电梯调度系统之间的接口(乘客发起乘坐电梯的请求,请求需要发送给电梯调度系统),电梯调度系统与电梯之间的接口(电梯调度系统需要将乘客的任务具体分配到某一个电梯上)。我们在接口设计方面,完全达到了:接口语义明确,接口名称,参数类型,读写权限等等

这一部分将在后续的接口设计与实现中详细描述。

2.3 Loose Coupling 松散耦合

耦合度指的是模块间的紧密程度。耦合度越低,模块间的紧密程度越松散,模块的独立性就越强。在设计过程中,我们始终保证:尽量使用数据耦合,少用控制耦合,限制公共耦合范围,坚决避免使用内容耦合。

在进行松散耦合时,我们结对项目中尽可能保证模块之间互相调用时,传递的是基本数据类型,为数据耦合。在乘客类与电梯调度系统之间的接口中,传递的是复合数据结构,为特征耦合(由于需要调用乘客类中的方法,所以不得不传递复合数据结构)。

所以,在我们的模块设计的过程中,我们耦合程度仅限于特征耦合与数据耦合,并没有使用控制耦合、公共耦合以及内容耦合。

Part3 模块接口的设计与实现过程

1. 类的设计和结构

设计三个类:Elevator电梯类,Passenger类,ElevatorSystem电梯控制系统类,三个类的作用如下:

  1. Elevator电梯类:成员变量包含电梯的基本属性,电梯服务楼层,电梯当前载重,电梯最大承重,电梯当前人数,电梯最大人数;方法包括,运行,设置电梯所处楼层,设置电梯的服务楼层,估算电梯到达目标楼层的时间,电梯运行函数,电梯运行到指定楼层,等待乘客进出的各种方法;
  2. Passenger乘客类:成员变量包含乘客的ID,乘客的种类,乘客的目标楼层和当前位置,乘客乘坐的电梯ID;方法包括,获得乘客的ID,重量,目标楼层,当前楼层。
  3. ElevatorSystem电梯控制系统类

2. 采用的电梯分配算法

电梯分配算法的思想是根据乘客在该层的等待时间进行电梯分配。通过估算乘客发起请求时刻各个电梯到达乘客所在楼层目前所需要的时长来分配电梯。

电梯分配算法的流程图:

在这里插入图片描述

例如,1号电梯到达乘客楼层估算约需要10s,2号电梯到达乘客楼层估算约需要8s,3号电梯和4号电梯不服务乘客楼层,经过时间比较,我们可以得出2号电梯的优先级最高,所以令2号电梯服务该乘客。各个电梯的计算时间方法位于Elevator::calctime函数中:

int Elevator::calctime(int passengerfloor) {
    //到此目标楼层计算时间,根据时间进行计算安排的优先级
    int Need_time = 0;
    int temp = CurrentFloor;
    //cout << "temp = " << temp << endl;
    if (ServeFloors[passengerfloor] == false) {
        return 100000;
    }
    if (Status == 0) {
        //电梯只在1楼静止
        Need_time = abs(passengerfloor - 1);
        return Need_time;
    }

    if (CurrentFloor > passengerfloor) {
        if (Status == 1) {
            // 1. 电梯往上走且人在电梯当前楼层的下面
            for (int i = 0; i < upsequence.size(); i++) {
                Need_time += (upsequence[i] - temp);
                temp = upsequence[i];
                //cout << "temp = " << temp << endl;
                Need_time += 10;
            }
            for (int i = 0; i < downsequence.size(); i++) {
                if (downsequence[i] >= passengerfloor) {
                    Need_time += (temp - downsequence[i]);
                    temp = downsequence[i];
                    //cout << "temp = " << temp << endl;
                    Need_time += 10;
                }
                else {
                    break;
                }
            }
            Need_time += (temp - passengerfloor);
        }

        else if (Status == 2) {
            // 2. 电梯往下走且人在电梯当前楼层的下面
            for (int i = 0; i < downsequence.size(); i++) {
                if (downsequence[i] >= passengerfloor) {
                    Need_time += (temp - downsequence[i]);
                    temp = downsequence[i];
                    Need_time += 10;
                }
                else {
                    break;
                }
            }
            Need_time += (temp - passengerfloor);
        }
    }
    else if (CurrentFloor == passengerfloor) {
        Need_time = 0;
    }
    else {
        if (Status == 1) {
            // 3. 电梯往上走且人在电梯当前楼层的上面
            for (int i = 0; i < upsequence.size(); i++) {
                if (upsequence[i] <= passengerfloor) {
                    Need_time += (upsequence[i] - temp);
                    temp = upsequence[i];
                    Need_time += 10;
                }
                else {
                    break;
                }
            }
            Need_time += (passengerfloor - temp);
        }
        else if (Status == 2) {
            // 4. 电梯往下走且人在电梯当前楼层上面
            for (int i = 0; i < downsequence.size(); i++) {
                Need_time += (temp - downsequence[i]);
                temp = downsequence[i];
                Need_time += 10;
            }
            for (int i = 0; i < upsequence.size(); i++) {
                if (upsequence[i] <= passengerfloor) {
                    Need_time += (upsequence[i] - temp);
                    temp = upsequence[i];
                    Need_time += 10;
                }
                else {
                    break;
                }
            }
            Need_time += (passengerfloor - temp);
        }
    }
    return Need_time;
}

3. 电梯调度算法:

在本次软件工程基础大作业——电梯调度中,我们采用调度算法有:

  • 基准调度算法(BUS算法)
  • 更高性能的调度算法(LOOK算法)
  • SCAN-EDF算法(实时电梯调度算法)
3.1 BUS算法思想:

BUS算法的思想是:将电梯当作公交车,从-1层一直到最高层(20层),每一层都停,并且开门,让乘客进出,然后关门,继续向上走。直到最高层,再向下。BUS算法是电梯调度算法中性能最差的算法。

在这里插入图片描述

3.2 LOOK算法思想:

LOOK 算法是扫描算法(SCAN,也是第2阶段中的基准调度算法)的一种改进。对LOOK算法而言,电梯同样在最底层和最顶层之间运行。 但当 LOOK 算法发现电梯所移动的方向上不再有请求时立即改变运行方向,而扫描算法则需要移动到最底层或者最顶层时才改变运行方向。

LOOK算法是操作系统中磁盘调度的一种算法。LOOK算法用于磁盘调度中时,是指读/写头在开始由磁盘的一端向另一端移动时,随时处理所到达的任何磁道上的服务请求,直到移到最远的一个请求的磁道上。一旦在前进的方向上没有请求到达,磁头就反向移动。在磁盘另一端上,磁头的方向反转,继续完成各磁道上的服务请求。这样,磁头总是连续不断地在磁盘的两端之间移动。

LOOK算法用于电梯调度中,电梯其实就是LOOK算法中的磁头,楼层上的请求就是磁道上的服务请求。这就是磁盘调度LOOK算法用于电梯调度的思想。

在这里插入图片描述

3.3 SCAN-EDF算法:

EDF算法(最早截止期优先算法)是每次服务离截止期最近的算法,把即将要截止(等待时间最长)的乘客纳入到服务队列当中的算法。

SCAN-EDF 算法是 SCAN 算法和 EDF 算法相结合的产物。SCAN-EDF 算法先按照 EDF 算法选择请求列队中哪一个是下一个服务对象,而对于具有相同时限的请求,则按照 SCAN 算法服务每一个请求。

Part4 UML

UML设计图如下所示:

在这里插入图片描述

Part5 Code Contract,Design by Contract 合约编程 契约式设计

Code Contract (合约编程)是一种设计软件的方法。DbC规范设计人员应该为软件组件定义正式的、精确的和可验证的接口规范。

在我们设计代码的过程中,遵循契约式设计包含三块的原则:前提条件、后置条件和不变式。前置条件发生在操作的最开始,后置条件发生在操作的最后,不变式实际上是前置条件和后置条件的交集。

在本次结对项目中,我们采用契约式设计的方法,可以帮助我们在结对项目中明确规定一个模块单元(具体到面向对象,类的实例),在调用某个操作前后应当属于何种状态。我们二人规定了每个模块的前置条件、后置条件和不变式。我们二人在语言层面做出了严格的参数检查和结果检查,保证模块内部变量的不变性。

Code Contract and Design by Contract 的优点:

  1. 保证了双方合作的协同性,双方共同履行结对项目的义务,使用结对项目的权力
  2. 严格的项目代码检查保证了结对项目的质量,双发有效的协作关系保证了结对项目效率
  3. 优先对整体框架、接口、模块进行定义,明确了模块和接口的调度方式,明确了代码的正确性

Code Contract and Design by Contract 的缺点:

  1. 由于结对项目双方需要提前进行分工协作,提前定义的接口和模块不能及时进行更改,项目模块接口的修改较为繁琐,一旦修改,可能会造成代码的混乱
  2. 双方共同编写,可能会造成代码的冗余性过高。
  3. 同时,双方的代码风格,代码习惯的不同,也可能会增加代码的不可读性。

在本次结对项目中,张驰同学和魏慧聪同学对结对项目:电梯调度负有共同的责任。在本次结对项目中,我们主要对以下几块内容使用Code Contract方法:

  • 在接口设计的过程中,张驰同学具有编码、设计的义务,魏慧聪同学具有测试、修改的义务
  • 在界面设计的过程中,魏慧聪同学具有界面编码、设计的义务,张驰同学具有测试、修改的义务

Code Contract方法保证了结对项目代码的质量,提供了软件工程的效率和质量。我们在调用接口前,对调用的条件做出了合理的规定,对函数返回值和触发条件的状态进行了明确,对代码之间的调用流程进行了详细的规定,尽可能避免使用Code Contract 和 DbC 造成的代码冗余量过高和代码混乱的问题。

Part6 程序的代码规范,设计规范

在本次结对项目:电梯调度的,我和魏慧聪设计了以下几条程序设计的代码规范:

  1. 模块和接口的命名方式采用英文命名
  2. 命名需要清楚表示功能,需要清楚的表示该模块负责的任务
  3. 需要在相对应的算法和函数时写明详细注释
  4. 需要严格进行缩进、换行

程序中的异常:

  1. 程序发生了数组越界,是因为没有考虑到-1层的情况。

    大楼总共21层(1-20, -1层),-1的异常情况,我们采用数组下标0来表示-1层,其余1-20层采用层号表示当前楼层

  2. 程序对用户的输入不能进行严格要求,在UI界面中,我们可以通过输入乘客的信息来控制乘客使用电梯,但是,如果用户再要求输入“int”类型的控件中输入字符串类型,程序会异常退出。

    我们之后为了解决异常,采用输出ERROR信息的方式提醒用户以正确的输入格式进行输入。

Part7 界面模块的详细设计过程UI

用户界面如下所示,其中包含如下模块:

  1. 显示系统时间模块,位于界面的最顶层
  2. 乘客信息,录入乘客的信息创造一个乘客,点击**“提交”**按钮来模拟乘客对电梯调度系统发起请求
  3. 显示当前采用算法所成功运送的乘客人数,运行时间,显示整个电梯系统运行的效率
  4. 四部电梯事件记录文本框,放置于右侧上方,其中包含四部电梯在运行过程中遭遇的事件记录
  5. 电梯模拟展示界面,位于UI界面的下方,展示四部电梯当前位于第几层
  6. 电梯情况描述文本框,描述四部电梯的信息。

在这里插入图片描述

Part8 界面模块与其他模块的对接

界面UI和各个模块采用QT开发框架设计实现。

首先创建一个 Qt Widgets Application 的项目类 elesys,创建窗体时选择基类 QMainWindow,生成的类名为 MainWindow,之后在窗体上编辑各种控件。所使用到的控件的类主要有 QLabel、QLineEdit、QPushButton 和 QTextBrower。最终生成的界面如Part 7中的图片所示。

窗口的初始化过程创建电梯系统对象,在电梯系统初始化过程中会创建四个电梯对象,分别对应四部电梯。电梯的类继承了 QThread 类,QThread 类提供了一个与平台无关的管理线程的方法,一个 QThread 对象管理一个线程。由于 Qthread 的执行从 run() 函数执行开始,因此需要在描述电梯的类Elevator中重写 run 函数,函数描绘电梯的运行过程。

综上,在主窗口UI创建过程中,同时建立了电梯系统对象 Elesys 和四个电梯线程 e[4],这四个电梯线程由电梯系统对象 Elesys 管理。

电梯对象的类中有接收乘客的方法 Look_Receive_Request 和 Scan_EDF_Receive_Request,分别表示以两种不同的调度方法接收乘客请求,在电梯系统实际运行过程中只会根据需求调用其中一种调度方法。

界面UI有编辑乘客体重、当前楼层、目标楼层等信息的输入框和提交按钮,按下提交按钮,触发槽函数 on_pushButton_clicked(),函数新创建一个乘客 Passenger 对象,并调用已存在的电梯系统对象 Elesys 使用其中一种调度算法接收乘客的需求。之后的调度分配工作由电梯调度系统完成。

界面 UI 的电梯事件记录文本框和电梯模拟展示分别对应主界面的 QTextBrower 和 QLabel 实例化对象。为了安全考虑,QT 不允许除窗口线程以外的子线程修改窗口中的任何控件。因此采用connect方法实现信号与槽函数的关联,当电梯对象或者电梯系统对象的状态改变时,使用关键字 emit 发送相关的已经定义的 signals 信号,窗口线程检测到信号后启动对应的槽函数修改 UI 控件文字内容或者其显示方式。

其中各种基本数据结构、类和对应的方法构成了 MVC 原则的模型组件,他们独立于用户界面,直接管理程序的数据、逻辑和规则;电梯事件记录文本框和电梯模拟展示分别表示了两种电梯运行信息的展示方式;用户界面负责接受乘客信息输入并转化为乘客模型载入电梯调度系统;各个组件之间的交互采用connect方法发送信号和运行槽函数解决。因此在整个界面的设计过程中,遵循了 MVC 设计模式。

Part9 描述结对的过程

在这里插入图片描述

张驰同学和魏慧聪同学在寒假期间通过腾讯会议对程序的实现进行讨论,结对共同完成了本次电梯调度的项目。

Part10 结对项目采用的合作方式 及 优缺点

在本次结对合作中,我们的合作方式包含桥梁沟通以及说服沟通的方式。在彼此给双方充分条件进行互相了解的前提下,对电梯调度项目进行合理的逻辑分析,来进行沟通交流。

  • 张驰同学的优点在于

    1. 思维敏捷,对整体的工作方式具有良好的思路,能在开始前构建起整体的结构
    2. 对各种算法有较为良好的的了解
    3. 时间规划能力强
  • 张驰同学的缺点在于

    1. 粗心,代码中会出现一部分小错误,需要及时地进行修正
    2. 办事情比较急躁
  • 魏慧聪同学的优点在于

    1. 细心,对程序运行中较为细致的地方比较注重,善于寻找程序中的错误
    2. 对代码的理解能力强,编写代码效率高
    3. 善于沟通,能够清楚的理解和表达想法
  • 魏慧聪同学的缺点在于

    1. 对代码整体的把控能力较为欠缺

我们采用三明治方法来改进彼此的缺点,三明治法则包括三层:

第一层为认同、肯定对方的优点与积极的一面;第二层为给对方的相应缺点的建议与批评;第三层为鼓励对方、支持对方和帮助对方改善缺点。

三明治方法既保证了对方的自尊心和积极性,还能恰当的指出对方的错误,让结对项目的双方共同进步。

Part11 其他收获

在本次结对项目:电梯调度中,我不仅参考了老师在Gitlab中的Wiki资料。同时,也在互联网中提前查阅了相关电梯调度的算法。由于本项目四部电梯的运行楼层不同,并没有找到直接可以使用的调度算法。在网上查阅电梯调度算法以及老师的Wiki资料中的算法外,我们共同设计出了给乘客分配电梯的电梯分配算法,将乘客分配到尽可能快速完成运行任务的电梯上。

在本次电梯调度:结对项目中,我们不仅丰富了软件工程基础的相关知识,还锻炼了自身的代码能力。同时,结对项目让我们更加丰富地了解了编程的经验和方法,也让我们学会了Git的使用。

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值