This design is based off of:
https://leetcode.com/discuss/interview-question/object-oriented-design/1612174/OOD-Elevator

Which is based off of:
https://tedweishiwang.github.io/journal/object-oriented-design-elevator.html

### Objects ### 
# Elevator
# Buttons
# External Buttons
# Internal Buttons
# Requests
# Elevator events
# Door
# Elevator Door
# Floor Door
# Displays
# Floor Number Screen 
# Floors 

### Data Sturcutres ### 
# upRequests priority queue
# downRequests priority queue
# Pending requests Dictionary 

### Object Relationships ### 
# Elevator has a Elevator Door, Internal Buttons, Floor Number screen
# Floors have floor door, floor number screen, external buttons
# Elevator Door is a door
# Internal buttons are buttons
# Floor number screens are displays

### Design Patterns ###
# Pub/Sub: Elevator is the subject and publisher, all buttons and displays sub 
# elevator to figure out what to display and when to light up
# Req/Rep: Buttons send elevator request which then generates a response

### Future work ###
# Multithreading, multi elevators
# Elevator controller/strategy that can be swapped out

from enum import Enum
from abc import ABC, abstractmethod
from turtle import up

NUMBER_OF_FLOORS = 10

class DoorType(Enum): 
  floor = "FLOOR"
  elevator = "ELEVATOR"

class DoorStatus(Enum): 
  open = "OPEN"
  closed = "CLOSED"


class Door(): 

  def __init__(self,typeD, location = None, status = DoorStatus.closed):
    self.location = location
    self.typeD = typeD
    self.location = location
    self.status = status


  def open(self): 
    self.status = DoorStatus.open

  def close(self): 
    self.status = DoorStatus.closed


class ElevatorDoor(Door): 
  def __init__(self): 
    super().__init__(DoorType.elevator)

class FloorDoor(Door):
  def __init__(self,location):
    super().__init__(DoorType.floor,location)

class ButtonType(Enum): 
  internal = "INTERNAL"
  external = "EXTERNAL"

class ButtonStatus(Enum): 
  pressed = "PRESSED"
  unpressed = "UNPRESSED"


class Button(ABC): 
  def __init__(self,typeB, status = ButtonStatus.unpressed):
    self.typeB = typeB
    self.status = status

  def press(self): 
    self.status = ButtonStatus.pressed
    self.sendRequest()

  @abstractmethod
  def sendRequest(self):
    pass


  def unpress(self): 
    # Pub published fulfilled requeset
    self.status = ButtonStatus.unpressed

class ElevatorGoToFloorButton(Button): 
  def __init__(self, goToFloor): 
    super().__init__(ButtonType.internal)
    self.goToFloor = goToFloor

  def sendRequest(self):
    if self.goToFloor > elevator.location: 
      request = Request(RequestType.internal, RequestDirection.up, elevator.location,self.goToFloor)
      elevator.request(request)
    else: 
      request = Request(RequestType.internal, RequestDirection.down, elevator.location,self.goToFloor)
      elevator.request(request)


class FloorButtonType(Enum): 
  up = "UP"
  down = "DOWN"

class FloorButton(Button):
  def __init__(self,typeFB,location):
    super().__init__(ButtonType.external)
    self.location = location
    self.typeFB = typeFB

  def sendRequest(self):
    print('Sending Floor Button Request',self.location,self.typeFB)
    if self.typeFB == FloorButtonType.up:  
      request = Request(RequestType.external, RequestDirection.up,self.location, self.location)
      elevator.request(request)
    
    else: 
      request = Request(RequestType.external, RequestDirection.down,self.location, self.location)
      elevator.request(request)

floor = FloorButton(FloorButtonType.up,0)

class RequestType(Enum): 
    internal = "INTERNAL"
    external = "EXTERNAL"

class RequestDirection(Enum): 
    up = 1
    down = 0 

class Request:
    def __init__(self,typeR, direction, origin, target): 
        self.typeR = typeR
        self.direction = direction
        self.origin = origin
        self.target = target

class ElevatorStatus(Enum): 
  down = 0 
  up = 1
  idle = 2

class Elevator: 
  def __init__(self,location = 0): 
    self.location = 0 
    self.status = ElevatorStatus.idle
    self.door = ElevatorDoor()
    self.goTobuttons = [ElevatorGoToFloorButton(i) for i in range(NUMBER_OF_FLOORS)]
    # self.display
    # self.open button
    # selt.close
    # phone, emergency
    # weight limit
    self.upRequests = []
    self.downRequests = []


  def run(self): 
    while self.upRequests or self.downRequests: 
      self.processRequests() 
  
  def processRequests(self): 
    if self.upRequests: 
      self.processUpRequests()
      self.processDownRequests()
    
    else:
      self.processDownRequests()

  def processUpRequests(self): 
    "Process uprequests by closest floor first"
    while self.upRequests: 
      # Set status to moving up
      # Pop closest up request off the stack 
      # Close Door
      # Move to request floor
      # Open door 
      # Wait a few seconds
      # Remove any up or down requests that happen to coincide with the current floor or add pending requests that coincide with origin 
      # Publish event for fulfilled request, unpressing buttons and changing displays 
      # close door
      self.status = ElevatorStatus.up
      current_request = self.upRequests.pop(0)
      self.door.close()
      self.location = current_request.target
      self.door.open()
      if current_request.target == current_request.origin: 
        print('Picking up people for Up Request, on floor:', self.location)

      else: 
        print('Letting people off for Up Request, on floor:',self.location)
      self.door.close()

    self.status = ElevatorStatus.idle

  def processDownRequests(self): 
    "Process downRequests by closest floor first"
    while self.downRequests: 
      # Set status to moving up
      # Pop closest up request off the stack 
      # Close Door
      # Move to request floor
      # Open door 
      # Wait a few seconds
      # Remove any up or down requests that happen to coincide with the current floor or add pending requests that coincide with origin 
      # close door
      self.status = ElevatorStatus.down
      current_request = self.downRequests.pop(0)
      self.door.close()
      self.location = current_request.target
      # Open Floor Door
      self.door.open()
      if current_request.target == current_request.origin: 
        print('Picking up people for Down Request, on floor:', self.location)

      else: 
        print('Letting people off for Down Request, on floor:',self.location)
      self.door.close()
    self.status = ElevatorStatus.idle


  def request(self,request):

    if request.direction == RequestDirection.up: 
      self.upRequests.append(request)
      self.upRequests.sort(key = lambda x: x.target)

    else: 
      self.downRequests.append(request)
      self.downRequests.sort(key = lambda x: -x.target)


class Floor: 
  def __init__(self,location): 
    self.location = location
    self.upButton = FloorButton(FloorButtonType.up,location)
    self.downButton = FloorButton(FloorButtonType.down,location)
    self.door = FloorDoor(location)

  def pressFloorButton(self,direction):
    if direction == FloorButtonType.up: 
      self.upButton.press()
    else:
      self.downButton.press()


elevator = Elevator()

Floors = [Floor(i) for i in range(NUMBER_OF_FLOORS)]


Floors[4].pressFloorButton(FloorButtonType.up)
elevator.location = 4
elevator.goTobuttons[2].press()
elevator.run()

print()
print("RUN 2")

elevator.goTobuttons[9].press()
elevator.goTobuttons[5].press()
elevator.goTobuttons[7].press()
elevator.goTobuttons[0].press()
elevator.goTobuttons[1].press()

elevator.run()

It has some differences, and pros and cons.

Future Work:
Implement smarter elevator controller to avoid bugs where requests are processed out of order.
Multiple elevators
Multiple threads checking for new requests, i.e. design a real concurrent system instead of this, .run method.

Please upvote if you liked it!

Comments (1)