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
, Order
and 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.
MenuItem
Class:
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.
Extra
Subclass:
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).
display
Method:
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:
Menu
Instances:
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):
The
selected_pizza
variable represents an instance of theMenuItem
class, encapsulating both the pizza's name and price.The
bill
variable is likely a part of a broader class, theOrder
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.
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)
).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 thepizza_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.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 thebill
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):
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.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 andint
to convert that string to an integer.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 thesize_menu
. This is done to ensure that the user's choice corresponds to a valid option in the menu.Updating Order and Bill:
If the user's input is valid, the code retrieves the corresponding
MenuItem
object representing the selected size from thesize_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
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.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.Selecting Extra Topping:
selected_extra_topping = extra_menu.items[extra_toppings_choice - 1]
accesses the chosen extra topping from theextra_menu.items
list.Note: Python lists are zero-indexed, so we subtract 1 from the user's choice to get the correct index.
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.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.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
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).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.Handling Invalid Input (Exception Handling):
The
try
block encompasses the user input and decision-making process.The
except ValueError:
block catches aValueError
if the user enters a non-numeric input. It provides an error message and prompts the user to enter a valid choice.Inner Loop (Selecting Extra Toppings):
If the user chooses to add extra toppings (
toppings_choice == 'y'
), the innerwhile
loop is entered, allowing the user to select extra toppings. The details of this inner loop were explained in a previous response.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 theorders_list
, representing a list of orders.
Explaining Order Class Integration in Pizza Ordering – OOP Overview
Creating an Order Object (
current_order
):current_order = Order(selected_pizza, selected_size, [])
creates an instance of theOrder
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).Adding the Order to the List of Orders (
orders_list
):orders_list.append(current_order)
adds the newly createdOrder
object (current_order
) to a list (orders_list
).This list serves as a collection of all the orders placed during the pizza ordering process.
Displaying the Current Bill and Order Details:
for order in orders_list:
iterates through eachOrder
object in the list.order.display()
is expected to be a method within theOrder
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 thatbill
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.
- 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.