CSC110 / lectures / CourseNote / Object-Oriented Modelling.py
Object-Oriented Modelling.py
Raw
# Some are wrong. Not fully implemented.

from dataclasses import dataclass
from typing import Optional  # Needed for the type annotation
import datetime  # Needed for the start and end times of the order


@dataclass
class Vendor:
    """A vendor that sells groceries or meals.

    This could be a grocery store or restaurant.

    Instance Attributes:
      - name: the name of the vendor
      - address: the address of the vendor
      - menu: the menu of the vendor with the name of the food item mapping to
              its price
      - location: the location of the vendor as (latitude, longitude)

    Representation Invariants:
      - self.name != ''
      - self.address != ''
      - all(self.menu[item] >= 0 for item in self.menu)
      - -90.0 <= self.location[0] <= 90.0
      - -180.0 <= self.location[1] <= 180.0
    """
    name: str
    address: str
    menu: dict[str, float]
    location: tuple[float, float]


@dataclass
class Customer:
    """A person who orders food."""
    name: str
    location: tuple[float, float]


@dataclass
class Courier:
    """A person who delivers food orders from vendors to customers.
    Instance Attributes:
      - name: the name of the courier
      - location: the location of the courier as (latitude, longitude)
      - current_order: the order that the courier is currently assigned to,
                       or None if the courier is not assigned to any order
    Representation Invariants:
      - (in English)
          IF self.current_order is not None,
          THEN the order's courier is equal to self
          (p => q) <=> ((not p) or q)
      - (in Python)
          (self.current_order is None) or (self.current_order.courier == self)
          (self.current_order is None) or (self.current_order.courier is self)  <--
        This one is better, because it's checking whether the objects are the same (in
        memory)
    """
    name: str
    location: tuple[float, float]
    current_order: Optional[Order] = None


@dataclass
class Order:
    """A food order from a customer.

    Instance Attributes:
      - customer: the customer who placed this order
      - vendor: the vendor that the order is placed for
      - food_items: a mapping from names of food to the quantity being ordered
      - start_time: the time the order was placed
      - courier: the courier assigned to this order (initially None)
      - end_time: the time the order was completed by the courier (initially None)

    Representation Invariants:
      - self.food_items != {}
      - all(self.food_items[item] >= 0 for item in self.food_items)
    """
    customer: Customer
    vendor: Vendor
    food_items: dict[str, int]
    start_time: datetime.datetime
    courier: Optional[Courier] = None
    end_time: Optional[datetime.datetime] = None


class FoodDeliverySystem:
    """A system that maintains all entities (vendors, customers, couriers, and orders).

    Representation Invariants:
        - self.name != ''
        - all(vendor == self._vendors[r].name for vendor in self._vendors)
        - all(customer == self._customers[c].name for customer in self._customers)
        - all(courier == self._couriers[c].name for courier in self._couriers)
    """
    # Private Instance Attributes:
    #   - _vendors: a mapping from vendor name to Vendor object.
    #       This represents all the vendors in the system.
    #   - _customers: a mapping from customer name to Customer object.
    #       This represents all the customers in the system.
    #   - _couriers: a mapping from courier name to Courier object.
    #       This represents all the couriers in the system.
    #   - _orders: a list of all orders (both open and completed orders).

    _vendors: dict[str, Vendor]
    _customers: dict[str, Customer]
    _couriers: dict[str, Courier]
    _orders: list[Order]

    def __init__(self) -> None:
        """Initialize a new food delivery system.

        The system starts with no entities.
        """
        self._vendors = {}
        self._customers = {}
        self._couriers = {}
        self._orders = []

    def add_vendor(self, vendor: Vendor) -> bool:
        """Add the given vendor to this system.

        Do NOT add the vendor if one with the same name already exists.

        Return whether the vendor was successfully added to this system.
        """
        if vendor.name in self._vendors:
            return False
        else:
            self._vendors[vendor.name] = vendor
            return True

    def add_customer(self, customer: Customer) -> bool:
        """Add the given customer to this system.

        Do NOT add the customer if one with the same name already exists.

        Return whether the customer was successfully added to this system.
        """
        # Similar implementation to add_vendor

    def add_courier(self, courier: Courier) -> bool:
        """Add the given courier to this system.

        Do NOT add the courier if one with the same name already exists.

        Return whether the courier was successfully added to this system.
        """
        # Similar implementation to add_vendor

    def place_order(self, order: Order) -> None:
        """Record the new given order.

        Assign a courier to this new order (if a courier is available).

        Preconditions:
            - order not in self.orders
        """

    def complete_order(self, order: Order) -> None:
        """Mark the given order as complete.

        Make the courier who was assigned this order available to take a new order.

        Preconditions:
            - order in self.orders
        """


class Event:
    """An abstract class representing an event in a food delivery simulation.

    Instance Attributes:
        - timestamp: the start time of the event
    """
    timestamp: datetime.datetime

    def __init__(self, timestamp: datetime.datetime) -> None:
        """Initialize this event with the given timestamp."""
        self.timestamp = timestamp

    def handle_event(self, system: FoodDeliverySystem) -> list[Event]:
        """Mutate the given food delivery system to process this event.

        Return a new list of new events created by processing this event.
        """
        raise NotImplementedError




class NewOrderEvent(Event):
    """An event representing a when a customer places an order at a vendor."""
    # Private Instance Attributes:
    #   _order: the new order to be added to the FoodDeliverySystem
    _order: Order

    def __init__(self, order: Order) -> None:
        self.timestamp = order.start_time
        self._order = order

    def handle_event(self, system: FoodDeliverySystem) -> list[Event]:
        """Mutate system by placing an order."""
        success = system.place_order(self._order)

        if success:
            completion_time = self.timestamp + datetime.timedelta(minutes=10)
            return [CompleteOrderEvent(completion_time, self._order)]
        else:
            # Try to place the order again in 5 minutes
            self._order.start_time = self.timestamp + datetime.timedelta(minutes=5)
            return [NewOrderEvent(self._order)]


class CompleteOrderEvent(Event):
    """When an order is delivered to a customer by a courier."""
    # Private Instance Attributes:
    #   _order: the order to be completed by this event
    _order: Order

    def __init__(self, timestamp: datetime.datetime, order: Order) -> None:
        Event.__init__(self, timestamp)
        self._order = order

    def handle_event(self, system: FoodDeliverySystem) -> list[Event]:
        """Mutate the system by recording that the order has been delivered to the customer."""
        system.complete_order(self._order, self._timestamp)
        return []


class GenerateOrdersEvent(Event):
    """An event that causes a random generation of new orders.

    Private Representation Invariants:
        - self._duration > 0
    """
    # Private Instance Attributes:
    #   - _duration: the number of hours to generate orders for
    _duration: int

    def __init__(self, timestamp: datetime.datetime, duration: int) -> None:
        """Initialize this event with timestamp and the duration in hours.

        Preconditions:
            - duration > 0
        """

    def handle_event(self, system: FoodDeliverySystem) -> list[Event]:
        """Generate new orders for this event's timestamp and duration."""
        events = []

        while ...:

            new_order_event = ...  # Create a randomly-generated NewOrderEvent
            events.append(new_order_event)

        return events