Python Project: Pizza Menu with Object - Oriented Programming Principles.

Photo by Nik Owens on Unsplash

Python Project: Pizza Menu with Object - Oriented Programming Principles.

An Object-Oriented Programming principles to create a modular and user-friendly pizza ordering system, emphasizing encapsulation, inheritance, and a clean organization of classes.


Classes and Objects

This is how I would as a beginner, display the pizza menu. It is still a procedural style of coding, where functions are used to organize and encapsulate related pieces of code. However, this project will be the first OOP project for the ordering pizza from the menu shown above.

Create classes to represent the different menu items and menus. Each class could have methods for displaying the menu or menu items. This code uses classes (MenuItem, Extra, Orderand Menu) to encapsulate related functionality and data, making it more object-oriented. Each class has a display method to print the information in a structured manner.

MenuItemClass:

The MenuItem class is defined with a constructor (__init__ method) that initializes two attributes - name and price.

The display method is defined to print the details of the menu item, including its name and price.

ExtraSubclass:

Extra is a subclass of MenuItem, which means it inherits from the MenuItem class.

The __init__ method of the Extra class overrides the __init__ method of the base class (MenuItem).

In the overridden constructor, the super().__init__(f'Extra {name}', price) line calls the constructor of the base class (MenuItem). It appends "Extra " to the given name before initializing the name and price attributes of the base class. This means that every instance of Extra will have a modified name with "Extra " prepended

Output:


Display the "Pizza Menu"

This part consists of the explanation in order for the output show like the picture below.

pizza_menu.items: Accesses the list of menu items within the pizza_menu instance.

[pizza_choice - 1]: Uses the user's input (pizza_choice) to index into the list of menu items.

Note that Python uses zero-based indexing, so subtracting 1 is necessary to match the list indices. .

price: Retrieves the price attribute of the selected menu item.

start_number: New Parameter in Menu Class:

start_number: This is a new parameter added to the Menu class. It represents the starting number for numbering the menu items. By default, it is set to 1.


Update in display method:

In the display method of the Menu class, I modified the loop that iterates over menu items to include the starting number when displaying each item.

The enumerate function now starts counting from self.start_number. So, when displaying the menu items, it prints the item number along with the item details.

Applying the Change to pizza_menu:

When creating the pizza_menu instance, I provided a start_number of 1. This means that the numbering for pizza items will start from 1.


Order Class:

Constructor (__init__method):

The class has a constructor that initializes three attributes: pizza, size, and extras.

pizza: Represents the type of pizza being ordered (an instance of a MenuItem class).

size: Represents the size of the pizza being ordered (an instance of a MenuItem class).

extras: Represents a list of extra toppings or additional items added to the pizza order (instances of Extra class).

displayMethod:

The class has a display method responsible for printing the order details.

It prints the total order by including the pizza name and size.

If there are any extra toppings (self.extras is not empty), it iterates over the list of extras and prints each extra's name and price.

Output:


Instances

This code creates instances of classes to represent menus and items for a pizzeria. Specifically:

MenuInstances:

pizza_menu: Represents a pizza menu with items like Margherita, Pepperoni, Veggie, and Meat Lover, each with its corresponding price. The items are numbered starting from 1.

extra_menu: Represents an extras menu with items like Pepperoni, Vegetables, Cheese, and Prosciutto, each with its corresponding price.

size_menu: Represents a sizes menu with items like Medium and Large, each with its corresponding price.

Display Each Menu:

The display method of each Menu instance is called, which prints the menu name and iterates through its list of items, calling the display method for each item.

This results in a structured display of the menu names, regular items, and extra items with their respective prices.


Implement three while loops to facilitate the order-making process for a pizza

Pizza Choice Loop - OOP in Pizza Ordering

Explanation in terms of Object-Oriented Programming (OOP):

  1. The selected_pizza variable represents an instance of the MenuItem class, encapsulating both the pizza's name and price.

    The bill variable is likely a part of a broader class, the Order class, where the total cost of the order is being tracked.

    The use of a while loop and exception handling allows the program to handle user input gracefully, guiding the user through the ordering process while handling potential errors.

  2. Condition Check (if 1 <= pizza_choice <= len(pizza_menu.items)):

    This is an if statement that checks if the user's input (pizza_choice) is within a valid range of pizza options. The range is determined by the number of items in the pizza menu (len(pizza_menu.items)).

  3. Accessing the Selected Pizza (selected_pizza = pizza_menu.items[pizza_choice - 1]):

    If the condition is true (i.e., the user's input is a valid pizza choice), the code proceeds to access the corresponding MenuItem object from the pizza_menu.items list.

    pizza_choice - 1 is used to account for the fact that Python uses zero-based indexing. The user sees choices numbered from 1, but the list index starts from 0.

  4. Updating the Bill (bill += selected_pizza.price):

    The price attribute of the selected pizza (selected_pizza.price) is added to the running total stored in the bill variable. This keeps track of the total cost of the order.

    Displaying Order Details (print(f'Order: {selected_pizza.name}, ${selected_pizza.price}.\n')):

    The details of the selected pizza are then printed to the console using an f-string. It includes the pizza name (selected_pizza.name) and its price (selected_pizza.price).

Overall, this code demonstrates the principles of encapsulation and error handling within an object-oriented design for a pizza ordering system.


Size Upgrade Loop - OOP in Pizza Ordering

Explanation in terms of Object-Oriented Programming (OOP):

  1. Displaying Size Options (for i, size_item in enumerate(size_menu.items, start=1):):

    The code uses a loop to enumerate through the size_menu.items, printing each available size option along with its corresponding price.

    This encapsulates the size options within the size_menu object, providing a clean separation of concerns and making the code more modular.

  2. User Input (size_upgrade = int(input("Size: "))):

    The code prompts the user to input their choice for an upgraded pizza size. It uses input to get a string from the user and int to convert that string to an integer.

  3. Input Validation (if 1 <= size_upgrade <= len(size_menu.items):):

    The code checks if the entered size_upgrade is within a valid range, specifically between 1 and the number of items in the size_menu. This is done to ensure that the user's choice corresponds to a valid option in the menu.

  4. Updating Order and Bill:

    If the user's input is valid, the code retrieves the corresponding MenuItem object representing the selected size from the size_menu.items list.

    The price of the selected size is then added to the running total (bill), representing the total cost of the order.

    Feedback to User (print(f'Order upgraded to: {selected_size.name}, ${selected_size.price}.\n')):

    The code provides feedback to the user by printing a message confirming the upgrade, including the name and price of the upgraded size.


Extra Toppings Loop - OOP in Pizza Ordering

After choosing a pizza and potential size upgrade, the user is asked if they want to add extra toppings. Let's break it down the explanation from the two while loops.

Inner while loop:

Summary
This code segment captures the user's choice for an extra topping, validates the choice to ensure it's within the valid range, updates the order details by adding the selected extra topping to the bill, prints a confirmation message to the user, and keeps a record of the selected extra toppings.
  1. User Input:

    input("Extra Topping: ") prompts the user to enter a number representing their choice of an extra topping.

    int(...) converts the user's input (which is initially a string) into an integer.

  2. Input Validation:

    if 1 <= extra_toppings_choice <= len(extra_menu.items): checks if the entered number is within the valid range of extra topping choices. This ensures the user selects a valid option.

  3. Selecting Extra Topping:

    selected_extra_topping = extra_menu.items[extra_toppings_choice - 1] accesses the chosen extra topping from the extra_menu.items list.

    Note: Python lists are zero-indexed, so we subtract 1 from the user's choice to get the correct index.

  4. Updating Bill:

    bill += selected_extra_topping.price adds the price of the selected extra topping to the total bill. This keeps track of the cost of the order.

  5. Providing Feedback:

    print(f'Extra Topping added: {selected_extra_topping.name}, ${selected_extra_topping.price}\n') prints a message confirming the addition of the selected extra topping, including its name and price.

  6. Updating Selected Extra Toppings List:

    selected_extra_toppings.append(selected_extra_topping) adds the selected extra topping to a list (selected_extra_toppings). This list could be used to keep track of all the extra toppings selected in the order.

Outer while loop:

Let's break down the provided code from the outer while loop in an Object-Oriented Programming (OOP) perspective:

Summary
The outer loop manages the overall flow of the pizza ordering process. It handles the user's decision regarding whether to add extra toppings, creates an order based on the user's choices, and updates the list of orders. Exception handling is used to manage errors in user input. This structure helps in organizing the pizza ordering process and reflects an OOP design by creating and managing objects representing orders.
  1. User Input for Extra Toppings:

    toppings_choice = input("3. Do you want to add extra toppings? (y, n)\n").lower() prompts the user to decide whether they want to add extra toppings (yes or no).

  2. Decision Based on User Input:

    if toppings_choice == 'y': initiates the process of adding extra toppings if the user chooses 'y'.

    elif toppings_choice == 'n': provides an option to skip adding extra toppings and breaks out of the outer loop if the user chooses 'n'.

    else: handles the case where the user enters an invalid input and breaks out of the outer loop.

  3. Handling Invalid Input (Exception Handling):

    The try block encompasses the user input and decision-making process.

    The except ValueError: block catches a ValueError if the user enters a non-numeric input. It provides an error message and prompts the user to enter a valid choice.

  4. Inner Loop (Selecting Extra Toppings):

    If the user chooses to add extra toppings (toppings_choice == 'y'), the inner while loop is entered, allowing the user to select extra toppings. The details of this inner loop were explained in a previous response.

  5. Creating an Order and Updating Order List:

    After the user has selected extra toppings, an Order object (current_order) is created with the selected pizza, size, and extra toppings.

    The current_order is appended to the orders_list, representing a list of orders.


Explaining Order Class Integration in Pizza Ordering – OOP Overview

  1. Creating an Order Object (current_order):

    current_order = Order(selected_pizza, selected_size, []) creates an instance of the Order class.

    The Order class is assumed to have a constructor (__init__) that takes parameters representing the selected pizza, selected size, and a list of selected extra toppings (initially an empty list in this case).

  2. Adding the Order to the List of Orders (orders_list):

    orders_list.append(current_order) adds the newly created Order object (current_order) to a list (orders_list).

    This list serves as a collection of all the orders placed during the pizza ordering process.

  3. Displaying the Current Bill and Order Details:

    for order in orders_list: iterates through each Order object in the list.

    order.display() is expected to be a method within the Order class that prints the details of the order, including the selected pizza, size, and any extra toppings.

    print(f'Total Price: ${bill}\n')

    After displaying the details of each order, the code prints the total price (bill), assuming that bill is a variable representing the cumulative cost of all orders.


Final Code

As we wrap up our Python Pizza journey, remember that OOP has structured this code like a well-organized pizza parlor. Encapsulating functions and data, OOP enhances readability, reusability, and maintainability. So, enjoy your Python Pizza, knowing that the principles of OOP have made your ordering experience as satisfying as that first delicious bite. Happy coding and happy eating!

# Python Pizza Code. Build an automatic pizza order program.
print("Thank you for choosing Python Pizza Deliveries!\n")

# Define a BASE CLASS for menu items
class MenuItem:
    # constructor "__init__" that initializes the name and price attributes.
    def __init__(self, name, price):
        self.name = name
        self.price = price

    # method to display the menu item details
    def display(self):
        print(f'{self.name}..... ${self.price}')


# Define a SUBCLASS for extra items, inheriting from MenuItem
class Extra(MenuItem):
    def __init__(self, name, price):
        # Call the constructor of the base and prepend "Extra" to the name
        super().__init__(f'Extra {name}', price)


# Define a class for a menu, with a list of menu items
class Menu:
    def __init__(self, name, items, start_number = 1):
        self.name = name
        self.items = items          
        self.start_number = start_number

    def display(self):
        # Display the menu name and iterate over each item to display details
        print(f'{self.name} Menu:')
        for i, item in enumerate (self.items, start = self.start_number):
            print(f'{i}. ', end = ' ')
            item.display()
        print()

# Define a class to represent orders
class Order:
    def __init__(self, pizza, size, extras):
        self.pizza = pizza
        self.size = size
        self.extras = extras

    def display(self):
        print(f'Total Order: {self.pizza.name} ({self.size.name})')

        if self.extras:
            print('Extra Toppings: ')
            for extra in self.extras:
                print(f' {extra.name} (${extra.price})')





# Create instances of Menu, MenuItem, and Extra to represent different menus and items
pizza_menu = Menu("Pizza", [
    MenuItem("Margherita", 10),
    MenuItem("Pepperon", 12),
    MenuItem("Veggie", 14),
    MenuItem("Meat Lover", 16)
], start_number= 1) # Start numbering from 1

extra_menu = Menu("Extras", [
    Extra("Pepperoni", 3),
    Extra("Vegetables", 5),
    Extra("Cheese", 6),
    Extra("Prosciutto", 7)
])

size_menu = Menu("Sizes", [
    MenuItem("Medium", 5),
    MenuItem("Large", 7)
])

# Display each menu
pizza_menu.display()
size_menu.display()
extra_menu.display()


# bill to iterate total price
bill = 0

# list to store orders
orders_list = []

# Create a while loop for the order-making process
# Ask for pizza choice
while True:
    try:
        # converts input into integers, catches non-numeric input.
        pizza_choice = int(input(f'1. Choose your pizza from our Pizza menu (1-{len(pizza_menu.items)}).\nYour pizza choice: '))
        if 1 <= pizza_choice <= len(pizza_menu.items):
            selected_pizza = pizza_menu.items[pizza_choice -1]
            bill += selected_pizza.price
            print(f'Order: {selected_pizza.name}, ${selected_pizza.price}.\n')

            # Break out of the pizza order loop after successful order
            break
        else:
            print("Invalid pizza number choice. Please choose 1, 2, 3, or 4\n")

    except ValueError:
        # Tells user to try again if ValueError is caught
        print("Wrong input. Please choose 1, 2, 3, or 4\n")

# Ask for sizes
while True:
    size_choice = input("2. Do you want to upgrade pizza sizes? (y, n)\n").lower()

    if size_choice == "y":
        print("Choose upgrade size: (1)Medium or (2)Large")
        for i, size_item in enumerate(size_menu.items, start=1):
            print(f'{i}. {size_item.name}.... (${size_item.price})')

        try:
            size_upgrade = int(input("Size: "))
            if 1 <= size_upgrade <= len(size_menu.items):
                selected_size = size_menu.items[size_upgrade -1]
                bill += selected_size.price
                print(f'Order upgraded to: {selected_size.name}, ${selected_size.price}.\n')
                break  # Exit the size upgrade loop if the input is valid
            else:
                print("Invalid size choice. Please choose 1 or 2.\n")
        except ValueError:
            print("Invalid input. Please enter a number.\n")

    elif size_choice == "n":
        print("No additional size upgrade.\n")
        break  # Exit the size upgrade loop if the input is 'n'
    else:
        print("Invalid. Please choose 'y' or 'n'\n")


# Ask for extra toppings
while True: 
    try:
        toppings_choice = input("3. Do you want to add extra toppings? (y, n)\n").lower()

        if toppings_choice == 'y':
            print("Choose extra toppings: \n")
            selected_extra_toppings = []  # Store selected extra toppings for the current order
            for i, extra_item in enumerate(extra_menu.items, start=1):
                print(f'{i}. {extra_item.name}.... (${extra_item.price})')

            while True:
                try:
                    extra_toppings_choice = int(input("Extra Topping: "))
                    if 1 <= extra_toppings_choice <= len(extra_menu.items):
                        selected_extra_topping = extra_menu.items[extra_toppings_choice - 1]
                        bill += selected_extra_topping.price
                        print(f'Extra Topping added: {selected_extra_topping.name}, ${selected_extra_topping.price}\n')
                        selected_extra_toppings.append(selected_extra_topping)

                    else:
                        print("Invalid choice. Please choose a valid option.\n")

                    # Ask if the user wants to add more extra toppings
                    more_toppings_choice = input("Add more extra toppings? (y, n)\n").lower()
                    if more_toppings_choice == 'n':
                        break  # Exit the inner loop if the user chooses 'n'

                except ValueError:
                    print("Invalid input. Please type the number.\n")

            current_order = Order(selected_pizza, selected_size, selected_extra_toppings)
            orders_list.append(current_order)

        elif toppings_choice == 'n':
            print("No additional toppings upgrade.\n")     
            break  # Exit the size upgrade loop if the input is 'n'

        else:
              print("Invalid. Please choose 'y' or 'n")
              break  # Exit the size upgrade loop if the input is 'n'
    except ValueError:
        print("Wrong input. Please choose 1, 2, 3, or 4\n")


current_order = Order(selected_pizza, selected_size, [])
orders_list.append(current_order)

# Display the current bill
for order in orders_list:
    order.display()
    print(f'Total Price: ${bill}\n')

Code Feedback

Code Feedback from ChatGPT for improvement.

  • Documentation and Comments:

Add comments or docstrings to explain the purpose and functionality of each class and method. This helps improve code readability, especially for others (or yourself) who might read your code later.

  • Error Handling:

Wile you have some basic error handling, you could further enhance it. For example, in the pizza choice input, you could handle cases where the user enters a non-integer value more explicitly.

  1. Consistent Naming:

Ensure consistent naming conventions throughout your code. For instance, you have size_choice and size_upgrade which might be confusing. Consider making the naming more consistent.

  • Encapsulation in the Order Class:

Consider encapsulating the logic for calculating the total order price within the Order class. This aligns with OOP principles by having related functionality within the class.

  • Flexible Input Handling:

Make the code more flexible by handling input cases more gracefully. For instance, in the size upgrade section, consider handling unexpected input types more explicitly.

  • Menu Expansion:

Allow for dynamic expansion of menus. Your current implementation is fixed, and if you want to add more items or categories, you'd need to modify the code. Consider making your code more modular to accommodate menu expansions without extensive changes.

  • Testing:

Consider adding test cases to ensure the correctness of your code, especially when making modifications. This can be done using Python's built-in unittest module or other testing frameworks.

  • Class Responsibilities:

Review the responsibilities of each class. For instance, the Menu class could potentially handle more display-related tasks, making it responsible for creating a more user-friendly interface.


Disclosure

This article was written with the assistance of ChatGPT developed by OpenAI. While I initiated the project idea, coded, and provided insights, ChatGPT played a role in generating certain parts of the content. The collaboration aims to combine human creativity with AI capabilities for informative and engaging content.