CS 3 - Elevator System Using State Pattern for LLD - IndianTechnoEra
Latest update Android YouTube

CS 3 - Elevator System Using State Pattern for LLD

Chapter 11: Case Study - Elevator System Using State Pattern

Series: Low Level Design for .NET Developers | Previous: Chapter 10: Advanced Concurrency & Caching


📖 Introduction

The Elevator System is a classic LLD problem that tests your understanding of:

  • State Pattern - Managing behavior based on current state
  • Concurrency - Handling multiple requests simultaneously
  • Scheduling Algorithms - Optimizing elevator movement
  • Event-Driven Design - Reacting to button presses and sensor inputs
  • SOLID Principles - Clean, maintainable architecture

In this case study, we'll build a complete elevator system that can handle multiple elevators, floor requests, and optimize movement.


1. Requirements Analysis

1.1 Functional Requirements

  • Multiple elevators serving multiple floors
  • Users can request elevator from any floor (external button)
  • Users can select destination floor inside elevator (internal button)
  • Elevator displays current floor and direction
  • Doors open/close automatically with safety sensors
  • Emergency stop and alarm functionality
  • Optimize elevator assignment based on proximity and direction
  • Handle overload condition (weight sensor)
  • Maintenance mode support

1.2 Non-Functional Requirements

  • Low latency for request handling
  • Fair scheduling (no starvation)
  • Energy efficiency (minimize unnecessary movement)
  • Safe handling of concurrent requests
  • Audit trail for maintenance

2. Domain Model

2.1 Core Enums

namespace ElevatorSystem.Domain
{
    public enum Direction
    {
        None,
        Up,
        Down
    }
    
    public enum ElevatorState
    {
        Idle,
        Moving,
        DoorOpen,
        DoorClosing,
        Emergency,
        Maintenance
    }
    
    public enum DoorState
    {
        Closed,
        Opening,
        Open,
        Closing
    }
    
    public enum RequestType
    {
        External,  // Request from floor buttons
        Internal   // Request from inside elevator
    }
    
    public enum ElevatorStatus
    {
        Operational,
        Maintenance,
        OutOfService
    }
}

2.2 Value Objects

namespace ElevatorSystem.Domain.ValueObjects
{
    public class Floor
    {
        public int Number { get; }
        
        public Floor(int number)
        {
            if (number < 1 || number > 100)
                throw new ArgumentException("Floor must be between 1 and 100");
                
            Number = number;
        }
        
        public static implicit operator int(Floor floor) => floor.Number;
        public static implicit operator Floor(int number) => new Floor(number);
        
        public override string ToString() => $"Floor {Number}";
        public override bool Equals(object obj) => obj is Floor floor && Number == floor.Number;
        public override int GetHashCode() => Number.GetHashCode();
    }
    
    public class Weight
    {
        public double Kilograms { get; }
        
        public Weight(double kilograms)
        {
            if (kilograms < 0)
                throw new ArgumentException("Weight cannot be negative");
                
            Kilograms = kilograms;
        }
        
        public static bool operator >(Weight a, Weight b) => a.Kilograms > b.Kilograms;
        public static bool operator <(Weight a, Weight b) => a.Kilograms < b.Kilograms;
        
        public override string ToString() => $"{Kilograms} kg";
    }
    
    public class Speed
    {
        public double MetersPerSecond { get; }
        
        public Speed(double metersPerSecond)
        {
            if (metersPerSecond < 0)
                throw new ArgumentException("Speed cannot be negative");
                
            MetersPerSecond = metersPerSecond;
        }
        
        public TimeSpan TimeToTravelFloors(int floorCount, double floorHeight = 3.0)
        {
            var distance = floorCount * floorHeight;
            var seconds = distance / MetersPerSecond;
            return TimeSpan.FromSeconds(seconds);
        }
    }
}

3. Elevator State Pattern Implementation

3.1 State Interface and Context

namespace ElevatorSystem.Domain.States
{
    // State interface
    public interface IElevatorState
    {
        string StateName { get; }
        Task EnterAsync(Elevator elevator);
        Task ExitAsync(Elevator elevator);
        Task OnFloorArrivedAsync(Elevator elevator, Floor floor);
        Task OnDoorOpenedAsync(Elevator elevator);
        Task OnDoorClosedAsync(Elevator elevator);
        Task OnRequestAsync(Elevator elevator, Request request);
        Task OnEmergencyAsync(Elevator elevator);
        Task OnMaintenanceAsync(Elevator elevator);
        Task OnOverloadAsync(Elevator elevator);
    }
    
    // Context - Elevator
    public class Elevator
    {
        private IElevatorState _currentState;
        private readonly List<Request> _pendingRequests = new();
        private readonly object _lock = new object();
        
        // Properties
        public int Id { get; }
        public Floor CurrentFloor { get; private set; }
        public Direction CurrentDirection { get; private set; }
        public DoorState DoorState { get; private set; }
        public Weight CurrentWeight { get; private set; }
        public int MaxCapacity { get; }
        public double MaxWeightKg { get; }
        public Speed Speed { get; }
        public ElevatorStatus Status { get; private set; }
        public List<int> FloorStops { get; private set; } = new();
        
        // Events
        public event EventHandler<ElevatorEventArgs> FloorArrived;
        public event EventHandler<ElevatorEventArgs> DoorOpened;
        public event EventHandler<ElevatorEventArgs> DoorClosed;
        public event EventHandler<ElevatorEventArgs> EmergencyActivated;
        
        public Elevator(int id, int startFloor = 1, double maxWeightKg = 1000, int maxCapacity = 10, double speedMps = 1.0)
        {
            Id = id;
            CurrentFloor = new Floor(startFloor);
            CurrentDirection = Direction.None;
            DoorState = DoorState.Closed;
            CurrentWeight = new Weight(0);
            MaxWeightKg = maxWeightKg;
            MaxCapacity = maxCapacity;
            Speed = new Speed(speedMps);
            Status = ElevatorStatus.Operational;
            
            // Start in Idle state
            _currentState = new IdleState();
            _currentState.EnterAsync(this).Wait();
        }
        
        public void SetState(IElevatorState newState)
        {
            Console.WriteLine($"Elevator {Id}: {_currentState.StateName} → {newState.StateName}");
            _currentState.ExitAsync(this).Wait();
            _currentState = newState;
            _currentState.EnterAsync(this).Wait();
        }
        
        public async Task MoveToFloorAsync(Floor targetFloor)
        {
            if (targetFloor.Number == CurrentFloor.Number)
            {
                await _currentState.OnFloorArrivedAsync(this, targetFloor);
                return;
            }
            
            CurrentDirection = targetFloor.Number > CurrentFloor.Number ? Direction.Up : Direction.Down;
            
            // Simulate movement
            var floorsToMove = Math.Abs(targetFloor.Number - CurrentFloor.Number);
            var travelTime = Speed.TimeToTravelFloors(floorsToMove);
            
            Console.WriteLine($"Elevator {Id}: Moving {CurrentDirection} from floor {CurrentFloor} to {targetFloor}");
            
            // Simulate passing intermediate floors
            for (int step = 1; step <= floorsToMove; step++)
            {
                var intermediateFloor = CurrentFloor.Number + (CurrentDirection == Direction.Up ? step : -step);
                await Task.Delay(travelTime / floorsToMove);
                
                var floor = new Floor(intermediateFloor);
                OnFloorArrived(floor);
                
                // Check if there's a pending request at this floor
                lock (_lock)
                {
                    if (_pendingRequests.Any(r => r.Floor.Number == floor.Number && 
                        (r.Type == RequestType.External && r.Direction == CurrentDirection)))
                    {
                        Console.WriteLine($"Elevator {Id}: Stopping at floor {floor} due to pending request");
                        await _currentState.OnFloorArrivedAsync(this, floor);
                        break;
                    }
                }
            }
            
            await _currentState.OnFloorArrivedAsync(this, targetFloor);
        }
        
        public void OpenDoors()
        {
            DoorState = DoorState.Opening;
            Console.WriteLine($"Elevator {Id}: Opening doors at floor {CurrentFloor}");
            
            // Simulate door opening time
            Task.Delay(1000).Wait();
            DoorState = DoorState.Open;
            OnDoorOpened();
            
            // Auto-close after delay
            Task.Delay(3000).ContinueWith(_ => CloseDoors());
        }
        
        public void CloseDoors()
        {
            DoorState = DoorState.Closing;
            Console.WriteLine($"Elevator {Id}: Closing doors");
            
            // Simulate door closing time
            Task.Delay(1000).Wait();
            DoorState = DoorState.Closed;
            OnDoorClosed();
        }
        
        public void AddRequest(Request request)
        {
            lock (_lock)
            {
                _pendingRequests.Add(request);
                Console.WriteLine($"Elevator {Id}: Added request - {request}");
            }
            
            _currentState.OnRequestAsync(this, request).Wait();
        }
        
        public Request GetNextRequest()
        {
            lock (_lock)
            {
                if (!_pendingRequests.Any())
                    return null;
                    
                // Optimize request selection based on current direction
                var sameDirectionRequests = _pendingRequests
                    .Where(r => r.Direction == CurrentDirection || CurrentDirection == Direction.None)
                    .OrderBy(r => CurrentDirection == Direction.Up 
                        ? r.Floor.Number 
                        : -r.Floor.Number)
                    .ToList();
                    
                if (sameDirectionRequests.Any())
                {
                    var request = sameDirectionRequests.First();
                    _pendingRequests.Remove(request);
                    return request;
                }
                
                // No requests in current direction, take closest
                var closest = _pendingRequests
                    .OrderBy(r => Math.Abs(r.Floor.Number - CurrentFloor.Number))
                    .First();
                _pendingRequests.Remove(closest);
                return closest;
            }
        }
        
        public bool HasPendingRequests() => _pendingRequests.Any();
        
        public void AddWeight(double weight)
        {
            CurrentWeight = new Weight(CurrentWeight.Kilograms + weight);
            
            if (CurrentWeight.Kilograms > MaxWeightKg)
            {
                _currentState.OnOverloadAsync(this).Wait();
            }
        }
        
        public void RemoveWeight(double weight)
        {
            CurrentWeight = new Weight(Math.Max(0, CurrentWeight.Kilograms - weight));
        }
        
        public void ActivateEmergency()
        {
            _currentState.OnEmergencyAsync(this).Wait();
        }
        
        public void EnterMaintenance()
        {
            _currentState.OnMaintenanceAsync(this).Wait();
        }
        
        private void OnFloorArrived(Floor floor)
        {
            CurrentFloor = floor;
            FloorArrived?.Invoke(this, new ElevatorEventArgs { ElevatorId = Id, Floor = floor });
        }
        
        private void OnDoorOpened()
        {
            DoorOpened?.Invoke(this, new ElevatorEventArgs { ElevatorId = Id, Floor = CurrentFloor });
        }
        
        private void OnDoorClosed()
        {
            DoorClosed?.Invoke(this, new ElevatorEventArgs { ElevatorId = Id, Floor = CurrentFloor });
        }
    }
    
    public class ElevatorEventArgs : EventArgs
    {
        public int ElevatorId { get; set; }
        public Floor Floor { get; set; }
        public Direction Direction { get; set; }
        public string Message { get; set; }
    }
}

3.2 Concrete States

// Idle State - Elevator waiting for requests
public class IdleState : IElevatorState
{
    public string StateName => "Idle";
    
    public async Task EnterAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id} is idle at floor {elevator.CurrentFloor}");
        await Task.CompletedTask;
    }
    
    public async Task ExitAsync(Elevator elevator)
    {
        await Task.CompletedTask;
    }
    
    public async Task OnFloorArrivedAsync(Elevator elevator, Floor floor)
    {
        // Already at floor
    }
    
    public async Task OnDoorOpenedAsync(Elevator elevator)
    {
        // Not applicable in idle state
    }
    
    public async Task OnDoorClosedAsync(Elevator elevator)
    {
        // Not applicable
    }
    
    public async Task OnRequestAsync(Elevator elevator, Request request)
    {
        Console.WriteLine($"Elevator {elevator.Id}: Received request while idle - {request}");
        
        // Change to moving state and process request
        elevator.SetState(new MovingState());
        
        // If request is external, move to that floor
        if (request.Type == RequestType.External)
        {
            await elevator.MoveToFloorAsync(request.Floor);
        }
        // If request is internal, doors are already open? This shouldn't happen in idle
    }
    
    public async Task OnEmergencyAsync(Elevator elevator)
    {
        elevator.SetState(new EmergencyState());
    }
    
    public async Task OnMaintenanceAsync(Elevator elevator)
    {
        elevator.SetState(new MaintenanceState());
    }
    
    public async Task OnOverloadAsync(Elevator elevator)
    {
        // In idle state, overload can't happen
    }
}

// Moving State - Elevator in motion
public class MovingState : IElevatorState
{
    public string StateName => "Moving";
    
    public async Task EnterAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id} is moving {elevator.CurrentDirection}");
        await Task.CompletedTask;
    }
    
    public async Task ExitAsync(Elevator elevator)
    {
        await Task.CompletedTask;
    }
    
    public async Task OnFloorArrivedAsync(Elevator elevator, Floor floor)
    {
        Console.WriteLine($"Elevator {elevator.Id}: Arrived at floor {floor}");
        
        // Check if this floor has pending requests
        var hasStop = elevator.FloorStops.Contains(floor.Number) || 
                      elevator.HasPendingRequests();
        
        if (hasStop)
        {
            elevator.SetState(new DoorOpenState());
            elevator.OpenDoors();
        }
        else
        {
            // Continue moving
            var nextRequest = elevator.GetNextRequest();
            if (nextRequest != null)
            {
                await elevator.MoveToFloorAsync(nextRequest.Floor);
            }
            else
            {
                elevator.SetState(new IdleState());
            }
        }
    }
    
    public async Task OnDoorOpenedAsync(Elevator elevator)
    {
        // Not applicable
    }
    
    public async Task OnDoorClosedAsync(Elevator elevator)
    {
        // Not applicable
    }
    
    public async Task OnRequestAsync(Elevator elevator, Request request)
    {
        // Add to pending requests while moving
        Console.WriteLine($"Elevator {elevator.Id}: Adding request while moving - {request}");
        
        // If request is in same direction and ahead, add to stops
        if (request.Type == RequestType.Internal ||
            (request.Direction == elevator.CurrentDirection &&
             (elevator.CurrentDirection == Direction.Up && request.Floor.Number > elevator.CurrentFloor.Number) ||
             (elevator.CurrentDirection == Direction.Down && request.Floor.Number < elevator.CurrentFloor.Number)))
        {
            elevator.FloorStops.Add(request.Floor.Number);
        }
    }
    
    public async Task OnEmergencyAsync(Elevator elevator)
    {
        // Stop at next floor and enter emergency
        elevator.SetState(new EmergencyState());
    }
    
    public async Task OnMaintenanceAsync(Elevator elevator)
    {
        elevator.SetState(new MaintenanceState());
    }
    
    public async Task OnOverloadAsync(Elevator elevator)
    {
        // Overload can't happen while moving
    }
}

// Door Open State - Doors are open for boarding
public class DoorOpenState : IElevatorState
{
    public string StateName => "Door Open";
    
    public async Task EnterAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id}: Doors open at floor {elevator.CurrentFloor}");
        
        // Process internal requests from passengers
        var internalRequests = elevator.GetNextRequest();
        while (internalRequests != null && internalRequests.Type == RequestType.Internal)
        {
            Console.WriteLine($"Elevator {elevator.Id}: Passenger selected floor {internalRequests.Floor}");
            internalRequests = elevator.GetNextRequest();
        }
    }
    
    public async Task ExitAsync(Elevator elevator)
    {
        await Task.CompletedTask;
    }
    
    public async Task OnFloorArrivedAsync(Elevator elevator, Floor floor)
    {
        // Already at floor
    }
    
    public async Task OnDoorOpenedAsync(Elevator elevator)
    {
        // Already open
    }
    
    public async Task OnDoorClosedAsync(Elevator elevator)
    {
        // Doors closed, move to next request
        var nextRequest = elevator.GetNextRequest();
        
        if (nextRequest != null)
        {
            elevator.SetState(new MovingState());
            await elevator.MoveToFloorAsync(nextRequest.Floor);
        }
        else
        {
            elevator.SetState(new IdleState());
        }
    }
    
    public async Task OnRequestAsync(Elevator elevator, Request request)
    {
        // Add request while doors are open
        if (request.Type == RequestType.Internal)
        {
            // Internal request from inside elevator
            elevator.FloorStops.Add(request.Floor.Number);
            Console.WriteLine($"Elevator {elevator.Id}: Added internal request to floor {request.Floor}");
        }
        else
        {
            // External request - add to pending
            elevator.AddRequest(request);
        }
    }
    
    public async Task OnEmergencyAsync(Elevator elevator)
    {
        elevator.SetState(new EmergencyState());
    }
    
    public async Task OnMaintenanceAsync(Elevator elevator)
    {
        elevator.SetState(new MaintenanceState());
    }
    
    public async Task OnOverloadAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id}: OVERLOAD detected! Weight: {elevator.CurrentWeight.Kilograms}kg");
        // Keep doors open until overload is resolved
        elevator.SetState(new OverloadState());
    }
}

// Overload State - Too much weight
public class OverloadState : IElevatorState
{
    public string StateName => "Overload";
    
    public async Task EnterAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id}: OVERLOAD! Please reduce weight.");
        // Keep doors open
        elevator.DoorState = DoorState.Open;
        
        // Alert and wait for weight reduction
        await Task.Delay(1000);
    }
    
    public async Task ExitAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id}: Overload resolved. Weight: {elevator.CurrentWeight.Kilograms}kg");
    }
    
    public async Task OnFloorArrivedAsync(Elevator elevator, Floor floor) { }
    public async Task OnDoorOpenedAsync(Elevator elevator) { }
    public async Task OnDoorClosedAsync(Elevator elevator) { }
    
    public async Task OnRequestAsync(Elevator elevator, Request request)
    {
        // Ignore requests during overload
        Console.WriteLine($"Elevator {elevator.Id}: Cannot process requests during overload");
    }
    
    public async Task OnEmergencyAsync(Elevator elevator)
    {
        elevator.SetState(new EmergencyState());
    }
    
    public async Task OnMaintenanceAsync(Elevator elevator)
    {
        elevator.SetState(new MaintenanceState());
    }
    
    public async Task OnOverloadAsync(Elevator elevator)
    {
        // Already in overload
    }
}

// Emergency State - Emergency situation
public class EmergencyState : IElevatorState
{
    public string StateName => "Emergency";
    
    public async Task EnterAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id}: EMERGENCY MODE ACTIVATED!");
        
        // Stop at nearest floor
        await elevator.MoveToFloorAsync(elevator.CurrentFloor);
        elevator.OpenDoors();
        
        // Alert monitoring system
        elevator.EmergencyActivated?.Invoke(elevator, new ElevatorEventArgs 
        { 
            ElevatorId = elevator.Id, 
            Message = "Emergency activated" 
        });
    }
    
    public async Task ExitAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id}: Exiting emergency mode");
    }
    
    public async Task OnFloorArrivedAsync(Elevator elevator, Floor floor) { }
    public async Task OnDoorOpenedAsync(Elevator elevator) { }
    public async Task OnDoorClosedAsync(Elevator elevator) { }
    
    public async Task OnRequestAsync(Elevator elevator, Request request)
    {
        // Only accept emergency override
        if (request.Type == RequestType.Internal && request.Floor.Number == -1) // Emergency override
        {
            elevator.SetState(new MovingState());
        }
    }
    
    public async Task OnEmergencyAsync(Elevator elevator) { }
    public async Task OnMaintenanceAsync(Elevator elevator) { }
    public async Task OnOverloadAsync(Elevator elevator) { }
}

// Maintenance State - Out of service
public class MaintenanceState : IElevatorState
{
    public string StateName => "Maintenance";
    
    public async Task EnterAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id}: MAINTENANCE MODE - Out of service");
        elevator.Status = ElevatorStatus.Maintenance;
        
        // Move to maintenance floor if not there
        if (elevator.CurrentFloor.Number != 1)
        {
            await elevator.MoveToFloorAsync(new Floor(1));
        }
        elevator.DoorState = DoorState.Closed;
    }
    
    public async Task ExitAsync(Elevator elevator)
    {
        Console.WriteLine($"Elevator {elevator.Id}: Exiting maintenance mode");
        elevator.Status = ElevatorStatus.Operational;
    }
    
    public async Task OnFloorArrivedAsync(Elevator elevator, Floor floor) { }
    public async Task OnDoorOpenedAsync(Elevator elevator) { }
    public async Task OnDoorClosedAsync(Elevator elevator) { }
    
    public async Task OnRequestAsync(Elevator elevator, Request request)
    {
        Console.WriteLine($"Elevator {elevator.Id}: Cannot process requests - in maintenance");
    }
    
    public async Task OnEmergencyAsync(Elevator elevator)
    {
        // Emergency override during maintenance
        elevator.SetState(new EmergencyState());
    }
    
    public async Task OnMaintenanceAsync(Elevator elevator) { }
    public async Task OnOverloadAsync(Elevator elevator) { }
}

4. Request and Dispatch System

4.1 Request Models

namespace ElevatorSystem.Domain
{
    public class Request
    {
        public int Id { get; }
        public Floor Floor { get; }
        public Direction Direction { get; }
        public RequestType Type { get; }
        public DateTime Timestamp { get; }
        
        private static int _nextId = 1;
        
        public Request(Floor floor, Direction direction, RequestType type)
        {
            Id = _nextId++;
            Floor = floor;
            Direction = direction;
            Type = type;
            Timestamp = DateTime.UtcNow;
        }
        
        public static Request ExternalRequest(Floor floor, Direction direction)
            => new Request(floor, direction, RequestType.External);
            
        public static Request InternalRequest(Floor floor)
            => new Request(floor, Direction.None, RequestType.Internal);
        
        public override string ToString()
        {
            return Type == RequestType.External 
                ? $"External request at {Floor} going {Direction}"
                : $"Internal request to {Floor}";
        }
    }
    
    // Dispatcher - Assigns elevators to requests
    public class ElevatorDispatcher
    {
        private readonly List<Elevator> _elevators;
        private readonly object _lock = new object();
        
        public ElevatorDispatcher(List<Elevator> elevators)
        {
            _elevators = elevators;
        }
        
        public Elevator Dispatch(Request request)
        {
            lock (_lock)
            {
                var availableElevators = _elevators
                    .Where(e => e.Status == ElevatorStatus.Operational)
                    .ToList();
                    
                if (!availableElevators.Any())
                    throw new InvalidOperationException("No operational elevators available");
                
                // For internal requests, return the elevator that made the request
                if (request.Type == RequestType.Internal)
                {
                    // This should be handled by the elevator itself
                    return null;
                }
                
                // Find best elevator based on scoring
                var bestElevator = availableElevators
                    .Select(e => new
                    {
                        Elevator = e,
                        Score = CalculateScore(e, request)
                    })
                    .OrderBy(x => x.Score)
                    .First().Elevator;
                    
                Console.WriteLine($"Dispatcher: Assigning request {request} to Elevator {bestElevator.Id} (Score: {CalculateScore(bestElevator, request)})");
                
                return bestElevator;
            }
        }
        
        private int CalculateScore(Elevator elevator, Request request)
        {
            int score = 0;
            var floorDistance = Math.Abs(elevator.CurrentFloor.Number - request.Floor.Number);
            
            // Distance factor (closer is better)
            score += floorDistance;
            
            // Direction factor (same direction is better)
            if (elevator.CurrentDirection == request.Direction)
                score -= 5;
            else if (elevator.CurrentDirection == Direction.None)
                score -= 2;
            else
                score += 10;
            
            // Request in path factor
            if (elevator.CurrentDirection == Direction.Up && 
                request.Direction == Direction.Up && 
                request.Floor.Number > elevator.CurrentFloor.Number)
                score -= 8;
            else if (elevator.CurrentDirection == Direction.Down && 
                     request.Direction == Direction.Down && 
                     request.Floor.Number < elevator.CurrentFloor.Number)
                score -= 8;
            
            // Load factor (prefer less loaded elevators)
            var loadPercentage = elevator.CurrentWeight.Kilograms / elevator.MaxWeightKg;
            score += (int)(loadPercentage * 20);
            
            return score;
        }
    }
}

5. Building and Floor Management

namespace ElevatorSystem.Domain
{
    public class Building
    {
        public string Name { get; }
        public int TotalFloors { get; }
        public List<FloorPanel> FloorPanels { get; private set; }
        public ElevatorDispatcher Dispatcher { get; private set; }
        public List<Elevator> Elevators { get; private set; }
        
        public Building(string name, int totalFloors, int elevatorCount)
        {
            Name = name;
            TotalFloors = totalFloors;
            Elevators = new List<Elevator>();
            FloorPanels = new List<FloorPanel>();
            
            // Create elevators
            for (int i = 1; i <= elevatorCount; i++)
            {
                var elevator = new Elevator(i, 1);
                elevator.FloorArrived += OnElevatorFloorArrived;
                elevator.DoorOpened += OnElevatorDoorOpened;
                elevator.DoorClosed += OnElevatorDoorClosed;
                elevator.EmergencyActivated += OnElevatorEmergency;
                Elevators.Add(elevator);
            }
            
            // Create floor panels
            for (int floor = 1; floor <= totalFloors; floor++)
            {
                var panel = new FloorPanel(new Floor(floor));
                panel.UpButtonPressed += OnUpButtonPressed;
                panel.DownButtonPressed += OnDownButtonPressed;
                FloorPanels.Add(panel);
            }
            
            // Create dispatcher
            Dispatcher = new ElevatorDispatcher(Elevators);
        }
        
        public void CallElevator(Floor floor, Direction direction)
        {
            var request = Request.ExternalRequest(floor, direction);
            var elevator = Dispatcher.Dispatch(request);
            elevator?.AddRequest(request);
        }
        
        private void OnUpButtonPressed(object sender, FloorEventArgs e)
        {
            Console.WriteLine($"Building: Up button pressed at {e.Floor}");
            CallElevator(e.Floor, Direction.Up);
        }
        
        private void OnDownButtonPressed(object sender, FloorEventArgs e)
        {
            Console.WriteLine($"Building: Down button pressed at {e.Floor}");
            CallElevator(e.Floor, Direction.Down);
        }
        
        private void OnElevatorFloorArrived(object sender, ElevatorEventArgs e)
        {
            // Update floor displays
            foreach (var panel in FloorPanels)
            {
                panel.UpdateDisplay($"Elevator {e.ElevatorId} arrived");
            }
        }
        
        private void OnElevatorDoorOpened(object sender, ElevatorEventArgs e)
        {
            Console.WriteLine($"Building: Elevator {e.ElevatorId} doors opened at {e.Floor}");
        }
        
        private void OnElevatorDoorClosed(object sender, ElevatorEventArgs e)
        {
            Console.WriteLine($"Building: Elevator {e.ElevatorId} doors closed at {e.Floor}");
        }
        
        private void OnElevatorEmergency(object sender, ElevatorEventArgs e)
        {
            Console.WriteLine($"Building: ALERT! Elevator {e.ElevatorId} emergency at {e.Floor}");
            // Send alert to monitoring system
        }
        
        public async Task SimulateUsageAsync()
        {
            var random = new Random();
            
            for (int i = 0; i < 50; i++)
            {
                var floor = new Floor(random.Next(1, TotalFloors + 1));
                var direction = random.Next(0, 2) == 0 ? Direction.Up : Direction.Down;
                
                // Don't go up from top floor or down from bottom floor
                if (floor.Number == TotalFloors) direction = Direction.Down;
                if (floor.Number == 1) direction = Direction.Up;
                
                CallElevator(floor, direction);
                
                // Random internal requests from elevators
                var randomElevator = Elevators[random.Next(Elevators.Count)];
                if (randomElevator.Status == ElevatorStatus.Operational)
                {
                    var targetFloor = new Floor(random.Next(1, TotalFloors + 1));
                    if (targetFloor.Number != randomElevator.CurrentFloor.Number)
                    {
                        var internalRequest = Request.InternalRequest(targetFloor);
                        randomElevator.AddRequest(internalRequest);
                    }
                }
                
                await Task.Delay(random.Next(2000, 5000));
            }
        }
    }
    
    public class FloorPanel
    {
        public Floor Floor { get; }
        public bool UpButtonLit { get; private set; }
        public bool DownButtonLit { get; private set; }
        
        public event EventHandler<FloorEventArgs> UpButtonPressed;
        public event EventHandler<FloorEventArgs> DownButtonPressed;
        
        public FloorPanel(Floor floor)
        {
            Floor = floor;
        }
        
        public void PressUp()
        {
            UpButtonLit = true;
            UpButtonPressed?.Invoke(this, new FloorEventArgs { Floor = Floor, Direction = Direction.Up });
            
            // Auto-reset after 5 seconds (simulating elevator arrival)
            Task.Delay(5000).ContinueWith(_ => UpButtonLit = false);
        }
        
        public void PressDown()
        {
            DownButtonLit = true;
            DownButtonPressed?.Invoke(this, new FloorEventArgs { Floor = Floor, Direction = Direction.Down });
            
            Task.Delay(5000).ContinueWith(_ => DownButtonLit = false);
        }
        
        public void UpdateDisplay(string message)
        {
            // Update floor display
            Console.WriteLine($"Floor {Floor} Display: {message}");
        }
    }
    
    public class FloorEventArgs : EventArgs
    {
        public Floor Floor { get; set; }
        public Direction Direction { get; set; }
    }
}

6. Control System and Simulation

namespace ElevatorSystem.Control
{
    public class ElevatorControlSystem
    {
        private readonly Building _building;
        private readonly ILogger<ElevatorControlSystem> _logger;
        private bool _isRunning;
        
        public ElevatorControlSystem(int totalFloors, int elevatorCount, ILogger<ElevatorControlSystem> logger = null)
        {
            _building = new Building("Main Tower", totalFloors, elevatorCount);
            _logger = logger;
        }
        
        public async Task StartAsync()
        {
            _isRunning = true;
            _logger?.LogInformation("Elevator Control System started");
            
            // Start monitoring
            await MonitorSystemAsync();
        }
        
        public async Task StopAsync()
        {
            _isRunning = false;
            _logger?.LogInformation("Elevator Control System stopped");
            await Task.CompletedTask;
        }
        
        public void CallElevator(int floor, Direction direction)
        {
            _building.CallElevator(new Floor(floor), direction);
        }
        
        public void SelectFloor(int elevatorId, int floor)
        {
            var elevator = _building.Elevators.FirstOrDefault(e => e.Id == elevatorId);
            if (elevator != null)
            {
                var request = Request.InternalRequest(new Floor(floor));
                elevator.AddRequest(request);
            }
        }
        
        public async Task SimulateRandomUsageAsync(int requestCount, int delayMs = 3000)
        {
            _logger?.LogInformation("Starting simulation with {RequestCount} requests", requestCount);
            
            var random = new Random();
            
            for (int i = 0; i < requestCount; i++)
            {
                var floor = random.Next(1, _building.TotalFloors + 1);
                var direction = random.Next(0, 2) == 0 ? Direction.Up : Direction.Down;
                
                if (floor == _building.TotalFloors) direction = Direction.Down;
                if (floor == 1) direction = Direction.Up;
                
                CallElevator(floor, direction);
                
                // Random internal requests
                if (random.Next(0, 2) == 0)
                {
                    var elevatorId = random.Next(1, _building.Elevators.Count + 1);
                    var targetFloor = random.Next(1, _building.TotalFloors + 1);
                    SelectFloor(elevatorId, targetFloor);
                }
                
                await Task.Delay(random.Next(delayMs / 2, delayMs));
            }
            
            _logger?.LogInformation("Simulation completed");
        }
        
        public async Task SimulateEmergencyAsync(int elevatorId)
        {
            var elevator = _building.Elevators.FirstOrDefault(e => e.Id == elevatorId);
            if (elevator != null)
            {
                _logger?.LogWarning("Simulating emergency on elevator {ElevatorId}", elevatorId);
                elevator.ActivateEmergency();
            }
            await Task.CompletedTask;
        }
        
        private async Task MonitorSystemAsync()
        {
            while (_isRunning)
            {
                await Task.Delay(5000);
                
                var status = GetSystemStatus();
                _logger?.LogInformation("System Status: {Status}", status);
            }
        }
        
        public string GetSystemStatus()
        {
            var sb = new StringBuilder();
            sb.AppendLine($"\n=== Elevator System Status ===");
            sb.AppendLine($"Building: {_building.Name}, Floors: {_building.TotalFloors}");
            
            foreach (var elevator in _building.Elevators)
            {
                sb.AppendLine($"  Elevator {elevator.Id}: Floor {elevator.CurrentFloor}, " +
                    $"Direction: {elevator.CurrentDirection}, State: {elevator.GetType()}, " +
                    $"Weight: {elevator.CurrentWeight.Kilograms}kg, " +
                    $"Pending Requests: {(elevator.HasPendingRequests() ? "Yes" : "No")}");
            }
            
            return sb.ToString();
        }
    }
}

7. Unit Tests

namespace ElevatorSystem.Tests
{
    public class ElevatorStateTests
    {
        [Fact]
        public async Task IdleState_OnRequest_ShouldStartMoving()
        {
            // Arrange
            var elevator = new Elevator(1);
            var request = Request.ExternalRequest(new Floor(5), Direction.Up);
            
            // Act
            elevator.AddRequest(request);
            
            // Assert
            await Task.Delay(100);
            Assert.True(elevator.HasPendingRequests());
        }
        
        [Fact]
        public async Task Elevator_MoveToFloor_ShouldArriveAtTarget()
        {
            // Arrange
            var elevator = new Elevator(1);
            var targetFloor = new Floor(5);
            bool arrived = false;
            
            elevator.FloorArrived += (s, e) =>
            {
                if (e.Floor.Number == targetFloor.Number)
                    arrived = true;
            };
            
            // Act
            await elevator.MoveToFloorAsync(targetFloor);
            
            // Assert
            Assert.True(arrived);
            Assert.Equal(targetFloor.Number, elevator.CurrentFloor.Number);
        }
        
        [Fact]
        public void Request_ExternalRequest_ShouldCreateCorrectRequest()
        {
            // Arrange
            var floor = new Floor(3);
            
            // Act
            var request = Request.ExternalRequest(floor, Direction.Up);
            
            // Assert
            Assert.Equal(floor, request.Floor);
            Assert.Equal(Direction.Up, request.Direction);
            Assert.Equal(RequestType.External, request.Type);
        }
        
        [Fact]
        public void ElevatorDispatcher_ShouldSelectBestElevator()
        {
            // Arrange
            var elevators = new List<Elevator>
            {
                new Elevator(1, 5),  // At floor 5
                new Elevator(2, 1),  // At floor 1
                new Elevator(3, 10)  // At floor 10
            };
            
            var dispatcher = new ElevatorDispatcher(elevators);
            var request = Request.ExternalRequest(new Floor(3), Direction.Down);
            
            // Act
            var selected = dispatcher.Dispatch(request);
            
            // Assert
            Assert.NotNull(selected);
            // Elevator 2 at floor 1 is closest to floor 3 going down
            Assert.Equal(2, selected.Id);
        }
        
        [Theory]
        [InlineData(0, 1000, true)]
        [InlineData(1500, 1000, false)]
        public void Elevator_WeightCheck_ShouldDetectOverload(double currentWeight, double maxWeight, bool shouldBeNormal)
        {
            // Arrange
            var elevator = new Elevator(1, maxWeight: maxWeight);
            var overloadDetected = false;
            
            elevator.AddWeight(currentWeight);
            
            // Act & Assert
            if (currentWeight > maxWeight)
            {
                Assert.Throws<InvalidOperationException>(() => elevator.ActivateEmergency());
            }
        }
    }
    
    public class DispatcherAlgorithmTests
    {
        [Fact]
        public void Dispatch_SameDirection_ShouldPreferElevatorInPath()
        {
            // Arrange
            var elevators = new List<Elevator>
            {
                new Elevator(1, 3) { Direction = Direction.Up },
                new Elevator(2, 8) { Direction = Direction.Up }
            };
            
            var dispatcher = new ElevatorDispatcher(elevators);
            var request = Request.ExternalRequest(new Floor(5), Direction.Up);
            
            // Act
            var selected = dispatcher.Dispatch(request);
            
            // Assert
            Assert.Equal(1, selected.Id); // Elevator 1 is in path
        }
        
        [Fact]
        public void Dispatch_OppositeDirection_ShouldScoreLower()
        {
            // Arrange
            var elevators = new List<Elevator>
            {
                new Elevator(1, 5) { Direction = Direction.Up },
                new Elevator(2, 5) { Direction = Direction.Down }
            };
            
            var dispatcher = new ElevatorDispatcher(elevators);
            var request = Request.ExternalRequest(new Floor(3), Direction.Down);
            
            // Act
            var selected = dispatcher.Dispatch(request);
            
            // Assert
            Assert.Equal(2, selected.Id); // Elevator 2 going same direction
        }
    }
}

8. Usage Example

public class Program
{
    public static async Task Main(string[] args)
    {
        Console.WriteLine("=== Elevator System Simulation ===\n");
        
        // Create control system with 10 floors and 3 elevators
        var controlSystem = new ElevatorControlSystem(10, 3);
        
        // Start the system
        await controlSystem.StartAsync();
        
        // Simulate random usage
        Console.WriteLine("\n--- Simulating Random Requests ---");
        await controlSystem.SimulateRandomUsageAsync(20, 2000);
        
        // Simulate emergency
        Console.WriteLine("\n--- Simulating Emergency ---");
        await controlSystem.SimulateEmergencyAsync(1);
        
        // Get final status
        Console.WriteLine("\n--- Final System Status ---");
        Console.WriteLine(controlSystem.GetSystemStatus());
        
        // Stop the system
        await controlSystem.StopAsync();
        
        Console.WriteLine("\nSimulation completed. Press any key to exit.");
        Console.ReadKey();
    }
}

9. Summary

In this case study, we've built a complete Elevator System applying:

  • State Pattern - Idle, Moving, DoorOpen, Overload, Emergency, Maintenance states
  • Strategy Pattern - Different dispatching algorithms
  • Observer Pattern - Floor panels listening to elevator events
  • SOLID Principles - Single responsibility, open/closed, dependency inversion
  • Event-Driven Design - Buttons, sensors, and display updates
  • Concurrency Handling - Multiple elevators, request queuing
  • Optimization Algorithms - Elevator dispatching based on distance and direction

📝 Practice Extensions:

  1. Add predictive scheduling - learn usage patterns to pre-position elevators
  2. Implement peak hour optimization algorithms
  3. Add energy-saving mode - reduce speed during low usage
  4. Implement voice control for accessibility
  5. Add destination dispatch system where users select floor before entering
  6. Implement real-time monitoring dashboard
  7. Add maintenance scheduling based on usage statistics

Happy Coding! 🚀

Post a Comment

Feel free to ask your query...
Cookie Consent
We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.
Oops!
It seems there is something wrong with your internet connection. Please connect to the internet and start browsing again.
AdBlock Detected!
We have detected that you are using adblocking plugin in your browser.
The revenue we earn by the advertisements is used to manage this website, we request you to whitelist our website in your adblocking plugin.
Site is Blocked
Sorry! This site is not available in your country.