A complete walkthrough of polymorphism, method overriding, and super() — explained step by step through a real steakhouse ordering system in El Nido, Palawan. Every class, every method, every design decision explained.
Welcome to El Nido Steak & Wine – a Python command-line program that simulates a high‑end steakhouse ordering experience in the beautiful town of El Nido, Palawan. Customers can choose from three premium steaks, three side dishes, one signature salad, and three wines – with the option to order wine by the bottle or by the glass. Behind this simple interactive menu lies a rich demonstration of object‑oriented programming: polymorphism and method overriding.
The program defines a base class Product with a name, price, and two methods: display() and get_price(). Four subclasses – Steak, SideDish, Salad, and Wine – inherit from Product. Each overrides display() to add an emoji and a descriptive label, making the menu visually distinct. The Wine class goes a step further: it overrides both display() and get_price() to accept an optional portion parameter, handling the two pricing models (bottle vs. glass) elegantly without duplicating code.
This guide walks through every line of the program, explaining not just what the code does, but why each design choice was made – from using super() in constructors, to validating user input with try/except, to the clever use of default arguments to achieve polymorphic behaviour with extra parameters.
super(), the DRY principle, robust input validation, and a complete order flow – all in one concise, real‑world program.
The program consists of one base class (Product) and four subclasses (Steak, SideDish, Salad, Wine). It displays a menu, lets the user choose one item from each category, calculates the total, and prints a detailed receipt. The Wine class stands out because its display() and get_price() methods accept an optional portion parameter – a clever extension of polymorphism.
name, price, display(), and get_price(). Serves as the blueprint for all menu items.display() to add a 🍖 emoji and "STEAK SPECIAL" label.display() to add a 🥔 emoji and "SIDE DISH" label.display() to add a 🥗 emoji and "VEGETABLE SALAD" label.bottle_price and calculates glass_price. Overrides display() and get_price() with an optional portion parameter to handle bottle/glass pricing.# This program uses only built-in Python features. # No import statements are needed.
Everything in this program – classes, input handling, loops, formatting – uses core Python. No external modules are required. This keeps the focus on OOP concepts and makes the code immediately runnable on any Python 3 installation.
class Product: def __init__(self, name, price): self.name = name self.price = price def display(self): return f"{self.name} - ₱{self.price:.2f}" def get_price(self): return self.price
Product is the foundation. It defines two attributes (name, price) and two methods. display() returns a simple formatted string, and get_price() returns the price. These methods are designed to be overridden by subclasses. The base class itself is never instantiated directly in this program – it serves purely as an abstract blueprint.
Notice that the constructor does not include type hints – this is a stylistic choice, but hints could be added without changing behaviour. The f"{self.price:.2f}" format ensures prices are always shown with two decimal places, perfect for Philippine Pesos.
class Steak(Product): def display(self): return f"🍖 STEAK SPECIAL: {self.name} - ₱{self.price:.2f}"
Steak inherits the constructor from Product without adding any new attributes. It overrides only the display() method, prepending a steak emoji and the label "STEAK SPECIAL". This is a minimal but clear example of polymorphism: the same method name (display) now produces different output depending on the object’s class.
class SideDish(Product): def display(self): return f"🥔 SIDE DISH: {self.name} - ₱{self.price:.2f}"
SideDish follows the same pattern as Steak but with a potato emoji and a different label. This symmetry demonstrates how easily you can add new categories without affecting existing code – the main program only needs to know that every product has a display() method.
class Salad(Product): def display(self): return f"🥗 VEGETABLE SALAD: {self.name} - ₱{self.price:.2f}"
Again, only display() is overridden. The Salad class has no new attributes. The consistency of this pattern highlights one of the benefits of polymorphism: you can treat all products uniformly when displaying the menu or receipt.
class Wine(Product): def __init__(self, name, bottle_price): super().__init__(name, bottle_price) self.bottle_price = bottle_price self.glass_price = round(bottle_price / 5) # approx. 5 glasses per bottle def display(self, portion="bottle"): if portion == "glass": return f"🍷 WINE (GLASS): {self.name} - ₱{self.glass_price:.2f}" else: return f"🍷 WINE (BOTTLE): {self.name} - ₱{self.bottle_price:.2f}" def get_price(self, portion="bottle"): if portion == "glass": return self.glass_price else: return self.bottle_price
The Wine class introduces two new attributes: bottle_price (stored also as the inherited price) and glass_price, which is approximated by dividing the bottle price by 5. The constructor uses super().__init__(name, bottle_price) to initialise the base attributes, then adds its own.
Both display() and get_price() are overridden with an optional portion parameter that defaults to "bottle". This is a clever design: it allows the methods to be called without the parameter (like other products) when a default is acceptable, and with the parameter when the user chooses a glass. The method signatures are still compatible with the parent class when called with no extra arguments – that’s polymorphism with a twist.
The round(bottle_price / 5) calculation is a simplification; in a real system you might store the glass price explicitly or calculate it based on a more accurate formula.
The main() function orchestrates the entire experience. It prints a welcome banner, displays each category using the polymorphic display() method, then enters input‑validation loops for each category.
while True: try: ch = int(input("\nChoose your STEAK (1-3): ")) - 1 if 0 <= ch < len(steaks): chosen_steak = steaks[ch] break print("Invalid! Choose 1-3.") except ValueError: print("Enter a number!")
Each category uses a similar loop: convert input to int, adjust to zero‑based index, check bounds, and store the selected object. The try/except catches non‑numeric entries gracefully. For the salad, there’s only one option, so the loop checks that the user enters 1.
The wine selection is followed by an additional loop to ask for the portion (glass or bottle). This choice is stored and later passed to display() and get_price().
print(f" • {chosen_steak.display()}") print(f" • {chosen_side.display()}") print(f" • {chosen_salad.display()}") print(f" • {chosen_wine.display(wine_portion)}")
Notice the last line passes the wine_portion argument. This demonstrates that even though the method signature is different, the call site adapts. The total is computed by summing get_price() for the first three items and chosen_wine.get_price(wine_portion) for the wine.
Polymorphism in Python means that the same method name can trigger different implementations depending on the object’s class. In this program, polymorphism is used in two ways:
display() method is called. The correct emoji and label appear without any if statements.Wine class accepts an extra parameter, yet can still be used like a regular Product when the default is used.display to runWhen steak.display() is called, Python looks at the class of steak (Steak), finds an overridden display, and executes it. If the method were not found in Steak, Python would look in the parent class Product.
For the wine line, chosen_wine.display(wine_portion) passes an argument. Python still resolves the method based on the object’s class (Wine) and calls that version with the provided argument. This is a perfect example of how polymorphism doesn’t require identical signatures – as long as the call matches the child’s signature, it works.
Wine can be used as a Product if we ignore the extra parameter (by using defaults). This principle is satisfied because the base methods still work with the child objects when called with the base signature.Overriding a method means providing a new implementation in a child class that replaces the parent’s version. In this program, all four subclasses override display(), and Wine also overrides get_price().
super()super() is used in the Wine constructor to call the parent Product constructor and initialise the base attributes. This avoids repeating the assignment of self.name and self.price. If you forget super().__init__, those attributes will never be set, leading to an AttributeError later.
In the overridden methods themselves, we don’t call super() because we are completely replacing the parent’s behaviour. However, if you wanted to extend the parent method (e.g., print the generic description and an emoji), you could call super().display() and then add your own text.
super().__init__ in WineIf you omit the parent constructor call, the base attributes (name, price) are never set. Always call super().__init__(...) when you override the constructor.
If you add a required parameter to an overridden method, code that calls the method with the parent signature will break. Use default arguments to maintain compatibility, as Wine does with portion="bottle".
try/exceptWrapping int(input()) in a try block catches non‑numeric entries gracefully. Always provide a helpful error message and continue the loop.
The emojis in display() methods make the menu more engaging and help distinguish categories at a glance – a small touch that improves user experience.
Solidify your understanding by extending the program:
Dessert class that inherits from Product and overrides display() with a 🍰 emoji. Add 2‑3 desserts to the menu and include them in the order flow.Wine class so that some wines are only available by the glass, some only by the bottle. Adjust the input validation to handle this.receipt_.json . Include customer name, phone, items with portions, and total. Use ensure_ascii=False to preserve emojis.class Product: def __init__(self, name, price): self.name = name self.price = price def display(self): return f"{self.name} - ₱{self.price:.2f}" def get_price(self): return self.price class Steak(Product): def display(self): return f"🍖 STEAK SPECIAL: {self.name} - ₱{self.price:.2f}" class SideDish(Product): def display(self): return f"🥔 SIDE DISH: {self.name} - ₱{self.price:.2f}" class Salad(Product): def display(self): return f"🥗 VEGETABLE SALAD: {self.name} - ₱{self.price:.2f}" class Wine(Product): def __init__(self, name, bottle_price): super().__init__(name, bottle_price) self.bottle_price = bottle_price self.glass_price = round(bottle_price / 5) def display(self, portion="bottle"): if portion == "glass": return f"🍷 WINE (GLASS): {self.name} - ₱{self.glass_price:.2f}" else: return f"🍷 WINE (BOTTLE): {self.name} - ₱{self.bottle_price:.2f}" def get_price(self, portion="bottle"): if portion == "glass": return self.glass_price else: return self.bottle_price def main(): print("="*60) print("WELCOME TO EL NIDO STEAK & WINE RESTAURANT") print("El Nido, Palawan, Philippines") print("Finest steaks, wines, sides & salads in paradise!") print("="*60) # Steaks print("\n🌟 AVAILABLE STEAKS:") steaks = [ Steak("Ribeye Steak", 1500), Steak("T-Bone Steak", 1200), Steak("Sirloin Steak", 1000) ] for i, item in enumerate(steaks, 1): print(f"{i}. {item.display()}") # Side dishes print("\n🥔 AVAILABLE SIDE DISHES:") sides = [ SideDish("Mashed Potatoes", 150), SideDish("Baked Potatoes", 200), SideDish("French Fries", 120) ] for i, item in enumerate(sides, 1): print(f"{i}. {item.display()}") # Salad print("\n🥗 VEGETABLE SALAD:") salads = [Salad("Fresh Vegetable Salad", 250)] for i, item in enumerate(salads, 1): print(f"{i}. {item.display()}") # Wines (bottle price shown; glass calculated) print("\n🍷 AVAILABLE WINES (bottle prices shown):") wines = [ Wine("Red Wine (House)", 800), Wine("White Wine", 700), Wine("Rosé Wine", 750) ] for i, item in enumerate(wines, 1): print(f"{i}. {item.display('bottle')} (glass ≈ ₱{item.glass_price})") print("\n" + "-"*60) print("Please make your selections:") # Steak choice while True: try: ch = int(input("\nChoose your STEAK (1-3): ")) - 1 if 0 <= ch < len(steaks): chosen_steak = steaks[ch] break print("Invalid! Choose 1-3.") except ValueError: print("Enter a number!") # Side dish choice while True: try: ch = int(input("Choose your SIDE DISH (1-3): ")) - 1 if 0 <= ch < len(sides): chosen_side = sides[ch] break print("Invalid! Choose 1-3.") except ValueError: print("Enter a number!") # Salad choice while True: try: ch = int(input("Choose your SALAD (1): ")) - 1 if ch == 0: chosen_salad = salads[0] break print("Only 1 option available!") except ValueError: print("Enter 1!") # Wine choice + portion while True: try: ch = int(input("Choose your WINE (1-3): ")) - 1 if 0 <= ch < len(wines): selected_wine = wines[ch] break print("Invalid! Choose 1-3.") except ValueError: print("Enter a number!") while True: portion = input(f"Would you like '{selected_wine.name}' per glass or per bottle? (glass/bottle): ").strip().lower() if portion in ["glass", "bottle"]: chosen_wine = selected_wine wine_portion = portion break print("Please type 'glass' or 'bottle'.") # Build order order_items = [chosen_steak, chosen_side, chosen_salad] total = sum(item.get_price() for item in order_items) total += chosen_wine.get_price(wine_portion) # Receipt print("\n" + "="*60) print("RECEIPT - El Nido Steak & Wine Restaurant") print("El Nido, Palawan, Philippines") print("-"*60) print("Your Order:") print(f" • {chosen_steak.display()}") print(f" • {chosen_side.display()}") print(f" • {chosen_salad.display()}") print(f" • {chosen_wine.display(wine_portion)}") print("-"*60) print(f"TOTAL AMOUNT: ₱{total:.2f}") print("\nPAYMENT POLICY:") print(" Payment must be in CASH ONLY") print(" Philippine Pesos (PHP) accepted") print(" No credit/debit cards or digital payments") print("\nThank you for dining with us!") print("Enjoy your meal in beautiful El Nido! 🌴🍷🥩") print("="*60) if __name__ == "__main__": main()
Stay connected for more Python guides, OOP tutorials, and tech content from the Philippines. Follow on your preferred platform:
Get new Python tutorials, OOP guides, and tech articles from Valleys & Bytes delivered to your inbox — no spam, ever.
🔒 No spam. Unsubscribe anytime. Your email is never shared.
You have walked through every line of El Nido Steak & Wine, from the base class to the polymorphic receipt. You now understand how method overriding, super(), and parameterised polymorphism work together to create a flexible, realistic ordering system.
Polymorphism is a cornerstone of object‑oriented design. The same principles you learned here appear in web frameworks, game engines, and data science libraries. You are now equipped to design systems that are both reusable and easy to extend.
Your next step? Take the extension challenges seriously. Add a Dessert class, implement wine‑only‑by‑glass logic, or save orders to JSON. Each challenge will deepen your understanding and give you code you can proudly show off.
💬 Leave a Comment
💡 Comments are stored locally in this session. Be respectful and constructive.