Roadmap › Low-Level Design (LLD) — Object Design, UML and Classic Problems in C# › Module: Classic LLD Problems
Lessons in this course (8)
6. Design an elevator system and a vending machine (State pattern)
Advanced★ Must knowLesson 6 of 8
Elevator: N elevators, requests from floors (up/down) and inside cars; a dispatcher assigns requests; each elevator moves in a direction serving stops in order (SCAN/"elevator algorithm").
csharp
public enum Direction { Idle, Up, Down }
public sealed class Elevator(int id)
{
public int Id { get; } = id;
public int Floor { get; private set; }
public Direction Direction { get; private set; } = Direction.Idle;
private readonly SortedSet<int> _up = [], _down = [];
public void AddStop(int floor) { if (floor >= Floor) _up.Add(floor); else _down.Add(floor); if (Direction == Direction.Idle) Direction = floor >= Floor ? Direction.Up : Direction.Down; }
public void Step() // called on each tick
{
if (Direction == Direction.Up && _up.Count > 0) { Floor = _up.Min; _up.Remove(Floor); }
else if (Direction == Direction.Down && _down.Count > 0) { Floor = _down.Max; _down.Remove(Floor); }
Direction = _up.Count > 0 ? Direction.Up : _down.Count > 0 ? Direction.Down : Direction.Idle;
}
public int Cost(int floor) => Math.Abs(Floor - floor) + (Direction == Direction.Idle ? 0 : 2);
}
public interface IDispatchStrategy { Elevator Choose(IReadOnlyList<Elevator> elevators, int floor); }
public sealed class NearestElevator : IDispatchStrategy
{
public Elevator Choose(IReadOnlyList<Elevator> e, int floor) => e.MinBy(x => x.Cost(floor))!;
}Vending machine with the State pattern — behaviour depends on the current state; each state class handles the actions:
csharp
public interface IVendingState
{
void InsertCoin(VendingMachine m, decimal amount);
void SelectProduct(VendingMachine m, string code);
}
public sealed class IdleState : IVendingState
{
public void InsertCoin(VendingMachine m, decimal amount) { m.Balance += amount; m.State = new HasMoneyState(); }
public void SelectProduct(VendingMachine m, string code) => Console.WriteLine("Insert money first");
}
public sealed class HasMoneyState : IVendingState
{
public void InsertCoin(VendingMachine m, decimal amount) => m.Balance += amount;
public void SelectProduct(VendingMachine m, string code)
{
var p = m.Inventory[code];
if (p.Stock == 0) { Console.WriteLine("Sold out"); return; }
if (m.Balance < p.Price) { Console.WriteLine($"Insert {p.Price - m.Balance} more"); return; }
p.Stock--; m.Balance -= p.Price;
Console.WriteLine($"Dispensed {p.Name}, change {m.Balance}");
m.Balance = 0; m.State = new IdleState();
}
}
public sealed class Product(string name, decimal price, int stock) { public string Name = name; public decimal Price = price; public int Stock = stock; }
public sealed class VendingMachine
{
public IVendingState State { get; set; } = new IdleState();
public decimal Balance { get; set; }
public Dictionary<string, Product> Inventory { get; } = [];
public void InsertCoin(decimal amount) => State.InsertCoin(this, amount);
public void Select(string code) => State.SelectProduct(this, code);
}