Java程序设计——题目集5~7总结与单部电梯调度问题分析

Java程序设计——题目集5~7总结与单部电梯调度问题分析

前言

经过了几周紧张的Java编程训练,我完成了题目集5到7的所有编程任务。这三次题目集涵盖了Java面向对象编程的核心内容,从基本的类设计到复杂的多类协作系统,不仅考察了编程基础,更锻炼了解决实际问题的能力。

题目集5

题目集5主要集中在Java继承与多态的应用,共包含8道编程题目。其中最具挑战性的是"单部电梯调度程序",这道题要求设计一个电梯类,包含电梯的最大楼层数、最小楼层数、当前楼层、运行方向、运行状态以及电梯内部和外部请求队列。电梯需要按照特定规则运行,优先处理同方向的请求,并在所有同方向请求处理完毕后处理反向请求。这个题目要求我们建立一个复杂的状态管理系统,同时处理多种请求逻辑,是对面向对象编程能力的全面检验。

题目集6

题目集6在题目集5的基础上进行了迭代升级,要求对电梯调度系统进行重构,遵循单一职责原则(SRP)。这次题目要求将原本单一的电梯类拆分为多个专业类:电梯类、乘客请求类、队列类以及控制类。此外,还增加了对错误输入的处理,如楼层数超限和重复请求的过滤。这个题目集强调了良好的软件设计原则,让我深入理解了类的职责划分和组件化设计的重要性。

题目集7

题目集7是三个题目集中设计最为完善的一部分。这次题目在题目集6的基础上再次迭代,引入了乘客类(Passenger)取代原有的乘客请求类,并改变了请求输入的格式。外部请求从之前的"请求楼层数,请求方向"修改为"请求源楼层,请求目的楼层",电梯处理外部请求后需要将请求目的楼层加入到内部请求队列。这种设计更接近实际电梯系统的工作方式,提高了模拟的真实性。

通过这三次题目集的递进式设计,我不仅学习了如何实现一个电梯调度系统,更理解了软件设计的演进过程和面向对象设计的核心原则。

设计与分析:单部电梯调度问题

系统设计演进

单部电梯调度问题经历了三次迭代,系统设计也随之演进。下面我将分析每次迭代的设计变化和改进。

第一次迭代:单一电梯类设计

在第一次迭代中,整个系统仅由一个电梯类实现,该类负责所有功能,包括状态管理、请求队列管理以及调度算法。虽然实现了基本功能,但这种设计违反了单一职责原则,导致电梯类承担了过多责任,不利于维护和扩展。

代码结构的主要问题是:

  • 电梯类既要处理电梯的状态管理,又要管理请求队列
  • 请求逻辑和电梯移动逻辑混合在一起
  • 缺乏良好的模块化设计

第二次迭代:多类协作设计

第二次迭代引入了多类设计,将系统拆分为以下几个主要组件:

  • 电梯类(Elevator):专注于电梯本身的属性和行为
  • 乘客请求类(Request):表示乘客的请求信息
  • 请求队列类(RequestQueue):管理电梯内部和外部的请求队列
  • 控制类(Controller):负责整体调度逻辑

这种设计符合单一职责原则,每个类只负责一项职责,大大提高了代码的可维护性和可扩展性。同时,新的设计还增加了对无效请求的处理,包括楼层数超限和重复请求的过滤。

第三次迭代:引入乘客类

第三次迭代将乘客请求类(Request)替换为乘客类(Passenger),进一步完善了系统设计。乘客类不仅包含源楼层和目的楼层信息,还可以计算请求方向。这种设计更接近实际电梯系统的工作方式,在处理外部请求后,会自动将目的楼层加入到内部请求队列,模拟乘客进入电梯后按下目的楼层按钮的行为。

代码度量分析

SourceMonitor提供了对代码复杂度、代码行数、方法调用深度等多项指标的分析,帮助我们客观评估代码质量的变化。

第一次迭代代码度量

第一次迭代中,由于所有功能都集中在电梯类中,导致类的行数过多,方法复杂度高,内聚性低,耦合度高。具体表现为:

  • 类的代码行数过多
  • 方法的圈复杂度较高
  • 继承深度低(单一类设计)

第二次迭代代码度量

第二次迭代后,拆分为多个类使得每个类的规模适中,职责明确,复杂度降低:

  • 各类的代码行数均衡
  • 方法的圈复杂度降低
  • 类之间形成了良好的关联关系

第三次迭代代码度量

第三次迭代进一步优化了设计,特别是引入乘客类替代请求类,使系统更加符合实际场景:

  • 代码总行数略有增加,但功能更完善
  • 类的职责更加清晰,内聚性进一步提高
  • 方法的平均行数和复杂度保持在合理水平

数据结构选择

在三次迭代中,数据结构的选择也发生了变化:

  1. 第一次迭代中,简单使用Queue接口实现请求队列
  2. 第二次迭代中,使用LinkedList实现RequestQueue类,分别存储内部请求和外部请求
  3. 第三次迭代中,LinkedList存储的元素类型从Integer和ExternalRequest变成了Passenger

LinkedList作为请求队列的实现有以下优势:

  • 支持高效的头部和尾部操作,适合队列先进先出的特性
  • 支持动态扩容,适应不确定数量的请求
  • 提供丰富的集合操作API,便于管理请求

核心算法分析

三次迭代中,电梯调度算法的核心思想保持一致,但实现方式有所不同:

  1. 初始方向确定:根据第一个请求相对于当前位置的方向确定电梯初始移动方向
  2. 电梯移动:电梯按当前方向逐层移动,每到达一层都检查是否需要停靠
  3. 停靠判断:检查当前楼层是否有需要处理的请求
  4. 方向调整:处理完当前方向所有请求后,如有反向请求则转向;否则进入IDLE状态

在第三次迭代中,算法增加了处理外部请求后将目的楼层加入内部请求队列的逻辑,这使得算法更加复杂但也更符合实际电梯运行逻辑。

关键方法分析

Controller类的processRequests方法

这是整个系统的核心方法,负责协调电梯运行和请求处理。方法流程如下:

  1. 确定电梯初始运行方向
  2. 电梯逐层移动
  3. 每到达一层时,检查是否需要停靠
  4. 如需停靠,处理当前楼层的请求
  5. 根据剩余请求情况调整电梯运行方向

ProcessRequests方法流程图

flowchart TD A[开始] --> B{请求队列为空?} B -->|是| C[结束] B -->|否| D[确定电梯初始运行方向] D --> E[输出初始状态] E --> F[检查当前楼层是否需要停靠] F --> G{当前楼层需要停靠?} G -->|是| H[打开电梯门] H --> I[处理当前楼层请求] I --> J[关闭电梯门] J --> K{请求队列为空?} G -->|否| K K -->|是| C K -->|否| L{电梯状态为IDLE?} L -->|是| C L -->|否| M[移动电梯到下一层] M --> N[检查当前楼层是否需要停靠] N --> O{当前楼层需要停靠?} O -->|是| P[打开电梯门] P --> Q[处理当前楼层请求] Q --> R[关闭电梯门] R --> S{请求队列为空?} O -->|否| S S -->|是| C S -->|否| T{电梯状态为IDLE?} T -->|是| C T -->|否| F

Elevator类的move方法

负责电梯的移动操作:

  • 根据当前方向调整楼层
  • 更新电梯状态
  • 输出当前楼层和方向信息

Controller类的shouldStop方法

决定电梯是否需要在当前楼层停靠:

  • 检查内部请求是否包含当前楼层
  • 检查外部请求中是否有当前楼层且方向相符的请求
  • 根据检查结果决定是否停靠

ShouldStop方法流程图

flowchart TD A[开始] --> B[获取当前楼层] B --> C[初始化停靠标志为false] C --> D[初始化方向变更标志为false] D --> E{当前楼层还有请求?} E -->|否| M{需要转向?} E -->|是| F[重置处理标志为false] F --> G{内部请求队列不为空 且 队首请求楼层==当前楼层?} G -->|是| H[设置停靠标志为true\n移除该内部请求\n设置处理标志为true] G -->|否| I{外部请求队列不为空 且 队首请求楼层==当前楼层?} H --> I I -->|否| L{有请求被处理?} I -->|是| J{队首请求方向==电梯方向 或 电梯方向==IDLE?} J -->|是| K1[设置停靠标志为true\n移除该外部请求\n设置处理标志为true] J -->|否| K2{没有同方向请求?} K2 -->|否| L K2 -->|是| K3[设置停靠标志为true\n设置方向变更标志为true\n更新电梯新方向\n移除该外部请求\n设置处理标志为true] K1 --> L K3 --> L L -->|是| L1[开门并关门] L -->|否| E L1 --> E M -->|是| N[更新电梯方向] M -->|否| O{电梯方向不是IDLE?} N --> Z[结束] O -->|否| Z O -->|是| P[检查当前方向是否还有请求] P --> Q{当前方向没有请求?} Q -->|否| Z Q -->|是| R[检查反方向是否有请求] R --> S{反方向有请求?} S -->|是| T[转向反方向] S -->|否| U[设置方向为IDLE] T --> Z U --> Z

RequestQueue类的方法

在第三次迭代中,RequestQueue类提供了管理请求队列的方法:

  • addInternalRequest:添加内部请求
  • addExternalRequest:添加外部请求
  • getInternalRequests:获取内部请求队列
  • getExternalRequests:获取外部请求队列

踩坑心得

在实现单部电梯调度系统的过程中,我遇到了一系列有趣的挑战和问题,这些经历为我提供了宝贵的编程经验和教训。

问题1:类设计职责不清

在第一次迭代中,我将所有功能都放在电梯类中,导致该类过于庞大,难以维护。当需要添加新功能或修复bug时,经常需要大幅修改代码,容易引入新的问题。

解决方案:按照第二次迭代的要求,将系统拆分为多个类,每个类负责单一职责。这种设计让代码结构更清晰,也更容易维护和扩展。

问题2:请求处理顺序不正确

在实现过程中,一个常见问题是电梯有时会忽略当前楼层的请求,而是先去处理其他楼层的请求,然后再回来。

解决方案:修改Controller类的shouldStop方法,优化停靠判断逻辑:

// 伪代码展示
boolean shouldStop(int floor) {
    // 检查内部请求
    if (queue.getInternalRequests().contains(floor)) {
        return true;
    }
    
    // 检查外部请求
    for (Passenger passenger : queue.getExternalRequests()) {
        if (passenger.getSourceFloor() == floor) {
            // 检查方向是否一致
            if (passenger.getDirection() == elevator.getDirection() || 
                elevator.getDirection() == Direction.IDLE) {
                return true;
            }
        }
    }
    
    return false;
}
输入:
1
20
<3,DOWN>
<2>
<5,UP>
<1>
<4,DOWN>
<3>
<2,DOWN>
<8>
<4>
END
错误输出
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Open Door # Floor 2
Close Door
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
Current Floor: 2 Direction: DOWN
Current Floor: 1 Direction: DOWN
Open Door # Floor 1
Close Door
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
Current Floor: 3 Direction: DOWN
Current Floor: 2 Direction: DOWN
Open Door # Floor 2
Close Door
正确输出:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Open Door # Floor 2
Close Door
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 2 Direction: DOWN
Current Floor: 1 Direction: DOWN
Open Door # Floor 1
Close Door
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
Current Floor: 3 Direction: DOWN
Current Floor: 2 Direction: DOWN
Open Door # Floor 2
Close Door

问题3:同一楼层多个请求处理不完整

当一个楼层既有内部请求又有外部请求时,电梯可能只处理其中一个请求就继续移动,导致另一个请求被遗漏。

解决方案:修改请求处理逻辑,确保处理完当前楼层的所有请求后才继续移动:

// 伪代码展示
void processCurrentFloorRequests(int floor) {
    boolean hasProcessed = false;
    
    // 处理内部请求
    if (removeInternalRequest(floor)) {
        hasProcessed = true;
    }
    
    // 处理外部请求
    Passenger passengerToRemove = null;
    for (Passenger passenger : queue.getExternalRequests()) {
        if (passenger.getSourceFloor() == floor && 
            (passenger.getDirection() == elevator.getDirection() || 
             elevator.getDirection() == Direction.IDLE)) {
            passengerToRemove = passenger;
            // 将目的楼层加入内部请求
            queue.addInternalRequest(passenger.getDestinationFloor());
            hasProcessed = true;
            break;
        }
    }
    
    if (passengerToRemove != null) {
        queue.getExternalRequests().remove(passengerToRemove);
    }
    
    if (hasProcessed) {
        elevator.openDoors();
    }
}
输入:
1
10
<3>
<3,UP>
<3,DOWN>
end
错误输出
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
正确输出:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Open Door # Floor 3
Close Door

问题4:方向判断逻辑有缺陷

在某些情况下,电梯会过早地改变方向,导致部分请求未能按预期处理。

解决方案:改进determineDirection方法,考虑所有请求:

// 伪代码展示
Direction determineDirection() {
    if (elevator.getDirection() == Direction.UP) {
        // 检查上方是否还有请求
        boolean hasRequestsAbove = false;
        
        for (Integer floor : queue.getInternalRequests()) {
            if (floor > elevator.getCurrentFloor()) {
                hasRequestsAbove = true;
                break;
            }
        }
        
        for (Passenger passenger : queue.getExternalRequests()) {
            if (passenger.getSourceFloor() > elevator.getCurrentFloor()) {
                hasRequestsAbove = true;
                break;
            }
        }
        
        if (hasRequestsAbove) {
            return Direction.UP;
        } else {
            // 检查下方是否有请求
            boolean hasRequestsBelow = false;
            
            for (Integer floor : queue.getInternalRequests()) {
                if (floor < elevator.getCurrentFloor()) {
                    hasRequestsBelow = true;
                    break;
                }
            }
            
            for (Passenger passenger : queue.getExternalRequests()) {
                if (passenger.getSourceFloor() < elevator.getCurrentFloor()) {
                    hasRequestsBelow = true;
                    break;
                }
            }
            
            if (hasRequestsBelow) {
                return Direction.DOWN;
            } else {
                return Direction.IDLE;
            }
        }
    } else if (elevator.getDirection() == Direction.DOWN) {
        // 类似的逻辑检查下方和上方的请求
        // ...
    } else {
        // IDLE状态,根据请求位置决定方向
        // ...
    }
}

经验总结

通过解决这些问题,我学到了几个重要的编程经验:

  1. 设计先行:良好的类设计是成功实现复杂系统的基础。每个类应该有清晰的职责,避免职责过度集中。
  2. 测试驱动:针对不同的输入场景进行测试,特别是边界情况和特殊情况,能够及早发现问题。
  3. 渐进式开发:从简单的实现开始,逐步添加功能和优化,是一种有效的开发策略。
  4. 重构优化:当发现设计或实现有缺陷时,不要犹豫,及时重构代码以适应新的需求和发现的问题。

改进建议

尽管三次迭代已经大大改进了电梯调度系统的设计,但仍有多个方面可以进一步优化。

1. 设计模式应用

当前实现已经体现了一些设计模式的思想,但可以更系统地应用以下设计模式:

  • 状态模式:将电梯的不同状态(停止、移动、开门、关门)封装为独立的状态类,简化状态转换逻辑。
  • 命令模式:将不同类型的请求封装为命令对象,统一处理方式。
// 伪代码展示:状态模式
interface ElevatorState {
    void move();
    void stop();
    void openDoors();
    void closeDoors();
}

class MovingState implements ElevatorState {
    // 实现移动状态的行为
}

class StoppedState implements ElevatorState {
    // 实现停止状态的行为
}

// 电梯类使用状态对象
class Elevator {
    private ElevatorState state;
    
    public void setState(ElevatorState state) {
        this.state = state;
    }
    
    public void move() {
        state.move();
    }
    
    // 其他方法
}

2. 多电梯支持

当前系统只支持单部电梯的调度。在实际应用中,多部电梯的协同调度更为常见,也更具挑战性。可以扩展系统以支持多电梯场景,实现更复杂的调度策略。

这需要引入一个中央调度器(Dispatcher),负责将请求分配给最合适的电梯,以最小化乘客等待时间和电梯移动成本。

// 伪代码展示
class ElevatorDispatcher {
    private List<Elevator> elevators;
    private List<RequestQueue> queues;
    
    public void dispatchRequest(Passenger passenger) {
        Elevator bestElevator = findBestElevator(passenger);
        int index = elevators.indexOf(bestElevator);
        queues.get(index).addExternalRequest(passenger);
    }
    
    private Elevator findBestElevator(Passenger passenger) {
        // 根据各种因素选择最合适的电梯
        // 如距离、当前方向、负载等
    }
}

3. 异常处理机制

当前实现在处理异常情况(如无效楼层请求)时比较简单,只是忽略这些请求。可以实现更完善的异常处理机制:

  • 记录详细日志,便于后期分析和调试
  • 在适当的地方使用自定义异常类,提高代码可读性
  • 实现错误恢复机制,提高系统鲁棒性
// 伪代码展示
class InvalidFloorException extends Exception {
    public InvalidFloorException(String message) {
        super(message);
    }
}

class RequestQueue {
    public void addInternalRequest(int floor) throws InvalidFloorException {
        if (floor > elevator.getMaxFloor() || floor < elevator.getMinFloor()) {
            throw new InvalidFloorException("Invalid floor: " + floor);
        }
        
        internalRequests.add(floor);
    }
    
    // 其他方法
}

5. 用户界面

当前系统使用命令行接口接收请求和显示电梯状态。在实际应用中,一个直观的图形用户界面(GUI)可以大大提高用户体验。

可以使用Java Swing或JavaFX开发一个简单的GUI,显示电梯的实时状态、楼层位置和请求队列,并允许用户通过图形界面发送请求。

总结

通过完成题目集5到7,特别是单部电梯调度这样的复杂问题,我在Java编程和软件设计方面有了显著的成长。

学习收获

  1. 面向对象设计能力:通过三次迭代,我深入理解了面向对象设计的核心原则,特别是单一职责原则和类的协作方式。
  2. 设计演进思想:学会了如何通过迭代设计不断改进系统,从单一类逐步演进到多类协作的完善系统。
  3. 代码重构技能:培养了重构代码的能力,能够识别设计缺陷并进行有效改进。
  4. 问题分析能力:提高了分析复杂问题的能力,能够将大问题分解为可管理的小问题。
  5. 测试思维:养成了全面测试的习惯,能够设计测试用例覆盖各种场景和边界条件。

需要进一步学习的方向

  1. 设计模式:虽然在实现中隐含使用了一些设计模式思想,但还需要系统学习各种设计模式,以及它们在实际项目中的应用。
  2. 并发编程:电梯调度系统在实际应用中涉及多线程和并发控制,这是一个需要深入学习的重要领域。
  3. 测试驱动开发:学习如何通过编写测试用例驱动开发过程,提高代码质量和开发效率。
  4. 性能优化:深入了解Java性能优化技术,如JVM调优、算法优化等,提高系统性能。

课程建议

基于本次题目集的学习体验,我有以下建议:

  1. 渐进式难度设计:题目集已经体现了很好的难度递进,未来可以考虑在每个题目集内部也实现更细致的难度递进。
  2. 更多实际案例:增加更多来自实际项目的案例,帮助学生理解理论知识在实践中的应用。
  3. 小组协作项目:引入小组协作项目,培养学生的团队协作能力和项目管理能力,例如多人合作开发一个更复杂的电梯系统。
  4. 代码评审环节:引入代码评审环节,让学生互相评审代码,学习他人的优秀实践和思路。
  5. 更多交流讨论:增加课堂讨论和在线交流的机会,让学生分享解题思路和遇到的问题。

通过这三次题目集的学习,我不仅提升了Java编程技能,更重要的是培养了解决复杂问题的思维方式和方法论。从单一类设计到多类协作,从基本功能到完善系统,这种渐进式的学习过程使我对软件设计和开发有了更深入的理解。这些能力不仅适用于Java编程,也是软件开发和工程实践的普遍原则,将对我未来的学习和工作产生深远影响。

posted @ 2025-04-20 22:28  Guardinary  阅读(41)  评论(0)    收藏  举报