现代软件工程 - 电梯调度系统项目总结
作业类型: 对编程项目总结作业2
项目仓库:
📋 摘要
本项目实现了一个智能电梯调度系统,采用客户端-服务器架构,将"算法控制"和"GUI显示"完全解耦。系统支持作为独立客户端对接电梯模拟服务器,既可以运行本组算法配合本组界面,也可以跨组互相对接。
核心特性:
- 基于分区驻守与 SCAN 顺扫策略的调度算法
- 使用评分派梯实现高效响应
- 完整的启动脚本与依赖说明
- 支持 Windows/Linux 跨平台运行
📁 目录结构
/repo_root
├── gui.py # 原始 GUI 组件库(监控系统主窗口、信号桥、事件回调)
├── gui_only.py # 纯 GUI 模式(仅显示,不控制;观察者模式)
├── algorithm_only.py # 纯算法模式(只控制,不显示;调度控制器)
├── start.bat # Windows 启动脚本(按序启动 Server→GUI→Algorithm)
├── start.sh # Linux 启动脚本(GUI模式)
├── start_no_gui.bat # Windows 无头模式(仅算法)
├── start_no_gui.sh # Linux 无头模式(仅算法)
├── requirements.txt # 依赖包清单
├── README.md # 项目使用与设计说明
└── result.json # 状态快照(tick/elevators/events/passengers 等)
1️⃣ PSP 计划表
说明: PSP(Personal Software Process)用于在编码前做时间预算与过程管理。以下是我们在开始实现程序前的时间估算,单位为小时。
| 阶段 | 子项 | 说明 | 估算耗时 | 实际耗时 | 差异原因 |
|---|---|---|---|---|---|
| 规划 | 计划 | 明确目标、范围、角色分工 | 2 | 2 | - |
| 需求分析 | 场景与约束梳理 | 电梯参数、事件模型、服务能力、接口边界 | 2 | 3 | 模拟器事件模型理解花费更多时间 |
| 高层设计 | 架构设计 | 信息隐藏、接口设计、松耦合方案、模块边界 | 2 | 3 | 接口边界拉齐与跨组对接讨论 |
| 详细设计 | 算法与数据结构 | 调度策略(SCAN + 分区 + 评分)、状态管理 | 6 | 7 | 评分函数维度调整与权重试验 |
| 编码 | 模块实现 | algorithm_only / gui_only / gui | 8 | 8 | - |
| 单元测试 | 测试用例 | 事件回放、边界情况、能耗统计 | 4 | 3 | 测试没有预想中困难 |
| 集成测试 | GUI对接算法 | 接口互通、状态同步 | 4 | 3 | 就绪标记与启动顺序等问题解决比较顺利 |
| 重构 | 需求变更适配 | 评分函数调整、状态机补强、异常处理 | 4 | 4 | - |
| 文档 | README/博客 | 使用说明、设计说明、对接说明 | 1 | 1 | - |
| 代码评审 | Pair Review | 双人走查、规范统一 | 2 | 2 | - |
| 发布 | 打包与脚本 | start.bat / start_no_gui.bat / 环境变量方案 | 3 | 3 | - |
2️⃣ 信息隐藏、接口设计、松耦合
🔒 信息隐藏 (Information Hiding)
算法控制器内部状态全部封装在算法侧,不暴露给 GUI 或其他模块:
unit_state、unit_heading、service_queuepending_calls、zone_assignmentmove_count、total_energy
外部仅通过以下接口交互:
- 电梯代理 API:
ProxyElevator.go_to_floor - 事件回调:
on_init、on_elevator_stopped等
GUI 只负责可视化,通过 SignalBridge 转换事件为界面信号,不依赖任何算法的内部实现细节。
🔌 接口设计 (Interface Design)
基于 elevator_saga 的统一事件回调接口:
python
on_init(elevators, floors)
on_passenger_call(passenger, floor, direction)
on_elevator_move(elevator, from_pos, to_pos, direction, status)
on_elevator_stopped(elevator, floor)
on_elevator_idle(elevator)
on_elevator_passing_floor(elevator, floor)
on_elevator_approaching(elevator, floor)
on_event_execute_start(event_type)
on_event_execute_end(event_type)
通过环境变量 ELEVATOR_CLIENT_TYPE 控制运行模式:
gui- 仅显示algorithm- 仅控制server- 启动模拟器
🔗 松耦合 (Loose Coupling)
架构优势:
- GUI 与算法分别是独立客户端,连接 Elevator Server
- GUI 可以对接任意算法,算法也可对接任意 GUI
- 启动脚本采用顺序启动与"就绪标记"机制,避免耦合与竞争
启动流程:
Server → GUI (等待就绪) → Algorithm
3️⃣ 重要模块接口设计与实现
🧠 算法控制器: IntelligentDispatchController
文件: algorithm_only.py
核心方法
main() - 入口函数
- 设置环境变量
ELEVATOR_CLIENT_TYPE=algorithm - 实例化控制器并启动
on_init(elevators, floors) - 初始化
- 初始化每台电梯状态 (idle/none)
- 计算驻守楼层 (home) 与服务分区 (zone)
- 电梯移动至驻守楼层 (immediate=True)
on_passenger_call(passenger, floor, direction) - 接收呼叫
- 将呼叫加入
pending_calls(up/down) - 触发智能派梯
_intelligent_dispatch(target_floor, direction)
_intelligent_dispatch(target_floor, direction) - 智能派梯
- 对所有电梯进行评分:
- 距离代价
- 方向一致性
- 空闲优先
- 分区匹配
- 负载约束
- 选择评分最高者执行
_assign_task(best_unit, target_floor)
_assign_task(unit, target_floor) - 分配任务
- 将目标楼层加入该电梯
service_queue - 若电梯空闲 (idle),立即执行
_execute_next
_execute_next(unit) - SCAN 调度
- 使用 SCAN 顺扫策略:
- 沿当前方向优先处理最近目标
- 若该方向无目标,则反向或回归队列首
- 最终调用
unit.go_to_floor(next_floor)
on_elevator_move(elevator, from_pos, to_pos, direction, status) - 移动统计
- 统计移动次数与能耗 (例如第 4 台电梯能耗权重更高)
on_elevator_stopped(elevator, floor) - 停靠处理
- 移除停靠楼层、清理
pending_calls - 若有队列或载客,继续执行
_execute_next - 否则回 idle 状态
on_elevator_idle(elevator) - 空闲处理
- 若仍有队列,继续执行
- 否则从
pending_calls中"抢最近呼叫" - 完全空闲则返回驻守楼层 (immediate=True)
🖥️ GUI 监控系统
文件: gui_only.py / gui.py
GUIOnlyController
观察者模式:
- 接收事件,不主动控制
- 避免触发模拟器重置
- 通过
SignalBridge发出日志与状态更新到界面
ElevatorMonitorSystem (QMainWindow)
主窗口组件:
- 电梯卡片栅格
- 日志面板
- 控制栏
- 持有
SignalBridge
start_simulation() - 启动监控
- 启动观察线程
run_simulation_observable
run_simulation_observable - 状态监听
- 持续监听模拟器状态
- 用 tick (模拟器时钟) 与
last_elevator_states检测刷新 - 安全处理算法重启导致的 tick 回退,避免界面崩溃
4️⃣ 算法关键与独到之处
🎯 核心策略
1. 分区与驻守
- 每台电梯初始化时分配服务区与驻守楼层
- 缩短响应距离
- 减少"全楼巡航"的无效移动
2. SCAN 顺扫策略
- 沿既定方向优先处理离当前最近的目标
- 减少方向反复与不必要停靠
- 降低乘客等待时间
3. 智能评分派梯
| 评分维度 | 说明 |
|---|---|
| 距离代价 | 越近越好 |
| 方向一致性 | 不反向优先 |
| 空闲优先 | idle 电梯优先响应 |
| 分区匹配 | 目标楼层在本电梯负责区更优 |
| 负载约束 | 接近满员/接近重量上限时拒绝外部新呼叫 |
4. 能耗统计
- 记录每台电梯移动次数与总能耗
- 为后续调度优化提供依据
💡 与"公交车(bus)算法"的对比
| 算法类型 | 特点 | 性能 |
|---|---|---|
| Bus 算法 | 层层停靠,上下循环 | 正确但慢,产生大量无效停靠 |
| 本项目算法 | 按需停靠,评分派梯+SCAN+分区 | 总旅行时间更短,停靠次数更少 |
适用场景: 在呼叫稀疏或分布较均匀的场景下优势明显
5️⃣ 需求变化的重构与回归测试
📝 需求变化
初版问题:
- 派梯仅考虑"最短距离"
- 忽略方向与负载
新需求:
- 方向一致性
- 分区优先级
- 能耗统计
🔧 重构策略
- 拆分评分函数各维度并权重化
- 补充
pending_calls与 idle 回流策略 - 统一在停靠事件中清理队列与
pending_calls,收敛状态管理 - GUI 观察模式修复:避免触发 reset,采用
run_simulation_observable持续监听
✅ 回归测试
测试方法:
- 固定 rush hour 场景进行事件回放
- 比对各版本总旅行时间与停靠次数
- 使用
result.json记录关键 tick 的状态快照 - 纯算法模式 (
start_no_gui.bat) 进行速度回归
🔀 PR/MR 流程
工作流程:
- Feature 分支 → PR → Review → Squash Merge
冲突处理:
由于其中之一并不熟悉 git 的使用流程,在结对编程的前期一直是一位在 commit,后期两个人在 commit 的时候会先同步 repo 再进行 commit,所以没有冲突与合并。
6️⃣ 代码质量保障
📏 代码规范
遵循 PEP 8:
- 命名规范
- 模块拆分清晰
- 函数关注单一职责
日志管理:
- 统一通过
_emit_log与SignalBridge输出 - 避免散乱打印
脚本规范:
- 使用 UTF-8 (chcp 65001) 避免中文乱码
- 路径使用带引号的变量
- 规避 Windows start 命令的坑
⚠️ 异常处理
GUI 保护:
- 观察模式对状态获取做 try/except
- "强制刷新间隔"保护
- 算法重启导致的 tick 回退不会让 UI 崩溃
脚本检查:
- 对 Python/conda 环境做显式检查
- 失败时提供明确提示
🛠️ 质量保障工具
建议集成:
flake8- 代码检查black- 代码格式化isort- 导入排序- CI 上跑单元测试与场景回归
📦 依赖管理
requirements.txt:
txt
flask>=2.0.0
flask-cors>=3.0.0
requests>=2.0.0
elevator_saga>=0.1.0
PyQt6>=6.5
注意: GUI 使用 PyQt6,Server/Client 通过 elevator_saga 连接电梯模拟服务器
7️⃣ 界面模块详细设计 (MVC)
🎨 设计目标
清晰展示多台电梯的运行状态,与算法分离,支持跨组对接:
- 楼层/方向/载客/能耗
- 呼叫信号与统计数据
🏗️ 主要类与职责
ElevatorMonitorSystem (QMainWindow)
- 主窗口管理
- 电梯卡片栅格
- 日志面板
- 控制栏
- 持有
SignalBridge
GUIOnlyController
- 连接模拟器
- 作为观察者接收事件
- 通过 signals (
log_message等) 传递到 UI
SignalBridge
- 抽象信号通道
- 降低 UI 与 Controller 的耦合
🔄 对接关系
环境变量控制:
ELEVATOR_CLIENT_TYPE=gui- 仅显示ELEVATOR_CLIENT_TYPE=algorithm- 仅控制ELEVATOR_CLIENT_TYPE=server- 启动模拟器
启动协调:
GUI 启动后写入就绪标记,脚本等待就绪再启动算法,避免"边启动边重置"的竞争。
🎯 MVC 实践
| 层次 | 组件 | 职责 |
|---|---|---|
| Model | Elevator Server 状态 | 电梯/楼层/乘客数据 |
| View | PyQt6 UI | 卡片/日志/统计界面 |
| Controller | GUIOnlyController Algorithm Controller | 事件处理与调度逻辑 |
📸 界面截图

8️⃣ 结对编程实践
📷 结对照片

🤝 采用的合作方式
混合模式:
- Driver-Navigator: Driver 专注编码实现,Navigator 监督方向与风险,查阅资料
- Ping-Pong Pairing: 一人写测试/用例,另一人完善实现,随后角色交换
⚖️ 结对编程优缺点
优点:
- ✅ 实时评审减少缺陷
- ✅ 知识流动更快
- ✅ 复杂设计更易落地
缺点:
- ⚠️ 时间协调成本高
- ⚠️ 节奏不合容易疲劳
- ⚠️ 水平差异导致沟通成本上升
👥 伙伴评价
优点:
- 响应迅速
- 擅长 vibe coding
- 沟通顺畅
缺点:
- 空闲时间较少,影响速度
🥪 三明治方法的结构化反馈
反馈结构:
- 肯定贡献
- 指出具体问题与影响
- 提供可执行的改进建议
9️⃣ 其它收获与讨论
💪 攻克技术难点
事件驱动与状态机复杂度:
- 将"评分派梯"与"队列执行"分离
- 采用 SCAN 控制方向与停靠
pending_calls统一收口外部呼叫- 避免在事件回调中直接"拍脑袋"改队列
跨组对接与启动顺序:
- 用就绪标记等待 GUI 再启动算法
- 避免模拟器 reset 导致 tick 回退
🤖 与 AI 工具配合
AI 的角色定位:
- 第三方参与者
- 资料检索
- API 差异比对
- 测试数据生成
使用原则:
Navigator 对 AI 建议二次筛选,防止"合理但错误"的结论进入关键路径。
📚 阅读与探索
参考资料:
- elevator_saga 与 elevator-py 的接口文档
- MVC 在事件驱动 GUI 场景的应用
🏆 KPI 比赛展望
若参与总旅行时间等指标比赛:
优势:
- ✅ 分区 + SCAN + 评分派梯可在热点楼层加速响应
- ✅ 能耗统计可用于调度细化 (高能耗梯少跑长距离)
劣势:
- ⚠️ 呼叫极度集中且频繁反向时,SCAN 需要更精细的队列管理
- ⚠️ 需要并列目标的批处理与合并停靠优化
🔄 角色切换频率的平衡
建议策略:
- ⏱️ 25--40 分钟或"完成一个可度量子任务"为一轮
- 📝 避免上下文丢失
- 🤝 轮换时由 Navigator 做简短交接
📈 结构化反馈促进团队成长
对基础差异较大的伙伴:
-
预设"任务包粒度":
- 强项者搭建骨架
- 弱项者补齐单元测试与边界情况
-
定期三明治反馈:
- 记录改进点并度量
- commit 数
- 测试覆盖
- 线上缺陷数
🚀 运行与对接说明
🖥️ 纯 GUI 模式 (观察者)
Windows:
bash
双击 start.bat
# 按序启动 Server → GUI → Algorithm
跨组对接:
- 先启动其他组的算法
- 运行本组
gui_only.py - 设置
ELEVATOR_CLIENT_TYPE=gui
⚙️ 纯算法模式 (无头)
Windows:
bash
运行 start_no_gui.bat
# 内部设置 ELEVATOR_CLIENT_TYPE=algorithm 并启动 algorithm_only.py
🔧 环境变量与依赖
系统要求:
- Python ≥ 3.8
- pip 安装 requirements.txt
环境变量 ELEVATOR_CLIENT_TYPE 控制运行模式:
server- 启动模拟器gui- 仅显示界面algorithm- 仅运行算法
⚠️ 兼容与注意事项
Windows 特殊处理:
- CMD 中文需
chcp 65001 - start 命令路径必须加引号
- 脚本使用
^&链接命令避免路径解析错误
Conda 环境:
- 脚本自动尝试激活指定环境
- 否则使用系统 Python