Introduction to Elevator Simulation in Object-Oriented Programming
Having studied C in the previous semester, transitioning to Java introduced a richer standard library and more sophisticated syntax. The recent assignments (5–7) emphasized both fundamental language constructs and advanced software design principles. Early problems focused on mastering regular expressions and input parsing, while later tasks centered around modeling an elevator system—requiring careful consideration of class structure, algorithm efficiency, state management, and maintainability.
Assignment 5: Basic Elevator Controller with Request Queues
The initial implementation featured two queues: one for internal floor selections and another for external calls tagged with direction. A core Elevator class managed movement logic using a finite state approach based on current position and pending requests.
enum Direction { UP, DOWN, STOPPED }
class Elevator {
private final int minFloor, maxFloor;
private int currentFloor;
private Direction direction;
private Queue<Integer> internalQueue;
private Queue<Map.Entry<Integer, Direction>> externalQueue;
public Elevator(int minFloor, int maxFloor) {
this.minFloor = minFloor;
this.maxFloor = maxFloor;
this.currentFloor = minFloor;
this.direction = Direction.STOPPED;
this.internalQueue = new LinkedList<>();
this.externalQueue = new LinkedList<>();
}
public void addInternalRequest(int floor) {
if (floor >= minFloor && floor <= maxFloor) {
internalQueue.offer(floor);
}
}
public void addExternalRequest(int floor, Direction dir) {
if (floor >= minFloor && floor <= maxFloor) {
externalQueue.offer(new AbstractMap.SimpleEntry<>(floor, dir));
}
}
public void processRequests() {
while (!internalQueue.isEmpty() || !externalQueue.isEmpty()) {
if (direction == Direction.STOPPED) determineDirection();
move();
if (shouldStop()) {
System.out.println("Open Door # Floor " + currentFloor);
removeRequests();
System.out.println("Close Door");
}
checkDirectionChange();
}
}
private void determineDirection() {
if (!internalQueue.isEmpty()) {
direction = internalQueue.peek() > currentFloor ? Direction.UP : Direction.DOWN;
} else if (!externalQueue.isEmpty()) {
direction = externalQueue.peek().getValue();
}
}
private void move() {
currentFloor += (direction == Direction.UP) ? 1 : -1;
System.out.println("Current Floor: " + currentFloor + " Direction: " + direction);
}
private boolean shouldStop() {
return (internalQueue.peek() != null && internalQueue.peek() == currentFloor)
|| (externalQueue.peek() != null && externalQueue.peek().getKey() == currentFloor
&& (externalQueue.peek().getValue() == direction
|| currentFloor == minFloor || currentFloor == maxFloor));
}
private void removeRequests() {
if (!internalQueue.isEmpty() && internalQueue.peek() == currentFloor) internalQueue.poll();
if (!externalQueue.isEmpty() && externalQueue.peek().getKey() == currentFloor) externalQueue.poll();
}
private void checkDirectionChange() {
boolean upPending = internalQueue.stream().anyMatch(f -> f > currentFloor)
|| externalQueue.stream().anyMatch(e -> e.getValue() == Direction.UP && e.getKey() > currentFloor);
boolean downPending = internalQueue.stream().anyMatch(f -> f < currentFloor)
|| externalQueue.stream().anyMatch(e -> e.getValue() == Direction.DOWN && e.getKey() < currentFloor);
if (direction == Direction.UP && !upPending) direction = downPending ? Direction.DOWN : Direction.STOPPED;
else if (direction == Direction.DOWN && !downPending) direction = upPending ? Direction.UP : Direction.STOPPED;
}
}
The main method parsed inputs using regex patterns like <\d+> for internal commands and <\d+,\s*(UP|DOWN)>, validating format before enqueueing.
Code Quality Assessment
- Total lines: 187 | Statements: 115
- Average cyclomatic complexity: 4.31
- Highest complexity method:
checkDirectionChange()(complexity = 15) - No comments present (0% documentation coverage)
- Nested block depth reached 6 levels
Issues identified included monolithic logic in direction evaluation, lack of modularity, and no separation between parsing and business logic.
Assignment 6: Modularization and Enhanced Request Modeling
This version refactored responsibilities into distinct classes: Request, RequestQueue, and Elevator. External and internal requests were now differentiated via a nullable direction field.
class Request {
private final int floor;
private final Direction direction; // null for internal
public Request(int floor, Direction direction) {
this.floor = floor;
this.direction = direction;
}
public boolean isExternal() { return direction != null; }
// getters...
}
The RequestQueue separated up/down external requests and internal targets, improving filtering during dispatch decisions.
class RequestQueue {
private final List<Request> internal = new LinkedList<>();
private final List<Request> upExternal = new LinkedList<>();
private final List<Request> downExternal = new LinkedList<>();
public void addRequest(Request r) {
if (!r.isExternal() && !contains(internal, r)) internal.add(r);
else if (r.getDirection() == Direction.UP && !contains(upExternal, r)) upExternal.add(r);
else if (r.getDirection() == Direction.DOWN && !contains(downExternal, r)) downExternal.add(r);
}
private boolean contains(List<Request> list, Request target) {
return list.stream().anyMatch(req -> req.getFloor() == target.getFloor());
}
}
The controller used proximity-based scheduling: it scanned all pending requests and selected the nearest one to initiate motion.
Design Improvements
- Introduced dedicated
Stateenum (MOVING,STOPPED) - Separated request types for directional filtering
- Added uniqueness checks when adding requests
- Improved readability through encapsulation
Metrics Overview
- Total lines: 317 | Statements: 187
- Comment ratio: 3.5%
- Number of classes: 6
- Highest complexity: 8 (
addRequest) - Average method complexity: 2.95
Despite better organization, performance bottlenecks remained due to linear searches across lists.
Assignment 7: Passenger-Centric Request Handling
This iteration modeled user intent explicitly by introducing a Passenger class representing journeys from source to destination. Internal requests were dynamically generated when passengers boarded.
class Passenger {
private final int src, dest;
public Passenger(int src, int dest) {
this.src = src;
this.dest = dest;
}
public Direction getTravelDirection() {
return (dest > src) ? Direction.UP : (dest < src) ? Direction.DOWN : Direction.IDLE;
}
}
The Controller class took charge of decision-making:
class Controller {
private final Elevator elevator;
private final RequestQueue queue;
private Passenger findNextRequest() {
Passenger closest = null;
int minDist = Integer.MAX_VALUE;
for (Passenger p : queue.getExternalRequests()) {
int d = Math.abs(p.getSourceFloor() - elevator.getCurrentFloor());
if (d < minDist) { minDist = d; closest = p; }
}
for (Passenger p : queue.getInternalRequests()) {
int d = Math.abs(p.getDestinationFloor() - elevator.getCurrentFloor());
if (d < minDist) { minDist = d; closest = p; }
}
return closest;
}
private boolean shouldContinueMoving() {
if (elevator.getDirection() == Direction.UP) {
return queue.getExternalRequests().stream().anyMatch(p ->
p.getSourceFloor() > elevator.getCurrentFloor() &&
p.getTravelDirection() == Direction.UP) ||
queue.getInternalRequests().stream().anyMatch(p ->
p.getDestinationFloor() > elevator.getCurrentFloor());
} else {
return queue.getExternalRequests().stream().anyMatch(p ->
p.getSourceFloor() < elevator.getCurrentFloor() &&
p.getTravelDirection() == Direction.DOWN) ||
queue.getInternalRequests().stream().anyMatch(p ->
p.getDestinationFloor() < elevator.getCurrentFloor());
}
}
}
When stopping at a floor, external passengers were transferred to internal queue upon boarding.
Complexity Profile
- Total lines: 375 | Statements: 212
- Comment percentage: 5.1%
- Classes: 7
- Max complexity: 15 (
shouldContinueMoving) - Average complexity: 2.86
- Maximum nesting depth: 7
Challenges Encountered
- Incorrect Output Fix Attempt: Modifying only part of flawed logic led to cascading errors, highlighting the need for holistic redesign over patching.
- Infinite Loop Issue: Missing termination conditions caused timeouts. Resolved by tightening loop exit criteria.
- Syntax Errors: Minor typos and compilation issues resolved through systematic debugging.
Critical Limitations and Enhancement Opportunities
- No Multi-Elevator Support: Architecture assumes single-unit operation.
- Enefficient Search: O(n) scanning of unsorted lists degrades performance at scale.
- Weak State Transitions: Direct jumps between states without validation (e.g., MOVING → OPEN_DOOR).
- Fragile Input Parsing: Minimal error recovery and no semantic validation (e.g., upward call from top floor).
- Lack of Thread Safety: Unsynchronized collections prevent concurrrent access.
- Poor Testability: Tight coupling hinders unit testing.
- Insufficient Logging: Only console output available, no audit trail.
Recommended Refinements
Architectural Upgrades
- Introduce a scheduler interface supporting various algorithms (SCAN, LOOK, SSTF).
- Decouple control logic using command or strategy pattern.
- Apply observer pattern for status monitoring and logging.
Performance Optimizations
- Replace linked lists with priority queues ordered by floor and direction.
- Implement sector-based indexing for large buildings.
- Cache directional demand flags to avoid repeated scans.
Functional Enhancements
- Add multi-elevator coordination with load balancing.
- Include overload detection and door timeout mechanisms.
- Support emergency stop and maintenance modes.
Code Quality Improvements
- Add comprehensive Javadoc and inline comments.
- Build robust input validators and exception handlers.
- Model state transitions as a formal FSM.
- Instrument detailed logging and metrics collection.
- Write unit tests with mock components.
Concurrency & UI
- Use thread-safe collections like
ConcurrentLinkedQueue. - Employ locks or atomic references where necessary.
- Adopt message queues (e.g., via
BlockingQueue) for decoupled request handling. - Develop a GUI showing real-time elevator positions and queue status.
- Provide usage statistics and response time reports.
Conclusion: Lessons in Software Engineering Growth
These assignments served as a progression from procedural thinking to object-oriented design maturity. Starting with basic syntax and regular expression skills, the focus evolved toward architectural concerns—modularization, scalability, and correctness under edge cases. Each iteration exposed weaknesses in prior assumptions, reinforcing that clean code requires continuous refactoring rather than incremental fixes. Key takeaways include:
- The importance of separating concerns early.
- How data structure choice directly impacts runtime efficiency.
- That debugging often reveals deeper design flaws.
- The value of writing testable, loosely coupled modules.
While the final systems remain simplistic compared to real-world elevators, the journey cultivated essential engineering instincts—balancing functionality, clarity, and extensibility in evolving codebases.