ADVERTISEMENT
Python · Polymorphism · Inheritance · El Nido

El Nido Steak & Wine

Python OOP · Polymorphism & Method Overriding Guide

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.

🧱 1 Base + 4 Subclasses 🍷 Wine with portion pricing 🔄 Polymorphism 🔧 Method Overriding 🔁 11 Sections 🐍 Python 3
SCROLL TO EXPLORE
INTRODUCTION

What Is El Nido Steak & Wine and Why Does It Matter?

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.

🐍
What you will learn from this guide: Polymorphism in action, method overriding with and without extra parameters, proper use of super(), the DRY principle, robust input validation, and a complete order flow – all in one concise, real‑world program.
💡
Who this guide is for: Python learners comfortable with basic classes and ready to understand how polymorphism and inheritance make code flexible and maintainable. No external libraries – only core Python.
ADVERTISEMENT
SECTION 01

Program Overview

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.

Class 1 — Product
Base class with name, price, display(), and get_price(). Serves as the blueprint for all menu items.
Class 2 — Steak
Inherits from Product. Overrides display() to add a 🍖 emoji and "STEAK SPECIAL" label.
Class 3 — SideDish
Inherits from Product. Overrides display() to add a 🥔 emoji and "SIDE DISH" label.
Class 4 — Salad
Inherits from Product. Overrides display() to add a 🥗 emoji and "VEGETABLE SALAD" label.
Class 5 — Wine
Inherits from Product. Adds bottle_price and calculates glass_price. Overrides display() and get_price() with an optional portion parameter to handle bottle/glass pricing.
📋Menu Display
Polymorphic display() calls for each category
🎯User Selections
Input validation loops for each category
🧾Receipt
display() again for order summary
💰Total
get_price() called polymorphically
SECTION 02

No Imports Needed — Pure Python

steak.py — no imports
# 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.

SECTION 03

The Product Base Class

class Product
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.

SECTION 04

Steak — Overriding display()

class Steak(Product)
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.

SECTION 05

SideDish — Another Simple Override

class SideDish(Product)
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.

SECTION 06

Salad — Yet Another Override

class Salad(Product)
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.

SECTION 07

Wine — Advanced Overriding with Parameters

class Wine(Product)
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.

SECTION 08

The Order Flow — Selection, Validation, and Receipt

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.

Input validation loop (steak example)
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().

Receipt printing
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.

SECTION 09

Polymorphism Deep Dive

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:

How Python decides which display to run

When 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.

📌
Liskov Substitution Principle: Any subclass must be usable where its parent is expected. Here, 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.
SECTION 10

Method Overriding & super()

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().

When to use 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.

SECTION 11

Common Mistakes & Best Practices

Forgetting to call super().__init__ in Wine

If you omit the parent constructor call, the base attributes (name, price) are never set. Always call super().__init__(...) when you override the constructor.

Changing the method signature incompatibly

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".

Validate user input with try/except

Wrapping int(input()) in a try block catches non‑numeric entries gracefully. Always provide a helpful error message and continue the loop.

Use meaningful emojis and labels

The emojis in display() methods make the menu more engaging and help distinguish categories at a glance – a small touch that improves user experience.

SECTION 12

Try It Yourself — Extension Challenges

Solidify your understanding by extending the program:

🟢 Beginner — Add Dessert class
Create a 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.
🟡 Intermediate — Wine by the glass only
Modify the Wine class so that some wines are only available by the glass, some only by the bottle. Adjust the input validation to handle this.
🔴 Advanced — JSON receipt
After payment, save the order details to a JSON file named receipt_.json. Include customer name, phone, items with portions, and total. Use ensure_ascii=False to preserve emojis.
SECTION 13

Complete Source Code

steak.py — Full Source
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()
Was this helpful?
Share This Guide
ADVERTISEMENT
FAQ

Frequently Asked Questions

El Nido Steak & Wine is a Python command-line program that simulates a steakhouse ordering system in El Nido, Palawan. It uses a base class Product and four subclasses (Steak, SideDish, Salad, Wine) to demonstrate polymorphism through method overriding. The Wine class has a special feature: it can display and calculate prices for both bottle and glass portions.
Each subclass overrides the display() method from Product. When the program prints an item, Python dynamically dispatches to the correct version – for Steak objects, it adds a 🍖 icon; for SideDish, 🥔; for Salad, 🥗; for Wine, it shows either a bottle or glass price based on an extra parameter. This allows uniform handling of different item types without type checking.
Wine can be ordered by the bottle or by the glass. To accommodate this, the overridden methods accept an optional portion parameter (defaulting to 'bottle'). This is a form of polymorphism via parameterisation – the method behaves differently based on the argument, while still conforming to the parent class signature (when called with no arguments, it defaults to bottle). It's a clean way to handle the two pricing models without duplicating classes.
super().__init__(name, bottle_price) calls the parent Product constructor to initialise the common attributes (name and price). It then adds its own bottle_price and calculates a glass_price (bottle_price // 5). This keeps the code DRY and ensures all attributes are set correctly.
Each selection is wrapped in a while True loop with try/except to catch non-numeric entries and validate the choice range. For the wine portion, the loop continues until the user enters 'glass' or 'bottle'. This makes the program robust against accidental typos.
Yes – because of polymorphism, you can add new subclasses (e.g., Dessert) that override display() and get_price(). The main() function would need to be updated to include the new menu category, but the printing and total calculation (which uses get_price()) would work automatically. The Wine class shows that you can even add extra parameters while keeping the core interface intact.
COMMUNITY

Comments & Discussion

💬 Leave a Comment

💡 Comments are stored locally in this session. Be respectful and constructive.

R
Rico Mercado
March 2026
The Wine class with the portion parameter is genius! I always wondered how to handle such cases without breaking polymorphism. The default argument keeps compatibility while adding flexibility. Thanks for this clear explanation.
G
Gina Reyes
March 2026
I added a Dessert class with a 🍰 emoji and it worked instantly with the existing receipt loop. Polymorphism is so powerful! Great tutorial, very practical.
CONNECT

Follow Glenn Junsay Pansensoy

Stay connected for more Python guides, OOP tutorials, and tech content from the Philippines. Follow on your preferred platform:

ADVERTISEMENT
🍷 You've Mastered Polymorphism — Now Build Your Own Steakhouse!

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.

🔁 Read Again 💻 Copy Source
🤖 AI-Powered Social Share

Let AI craft the perfect caption for sharing this article. Pick your platform and tone, then generate a ready-to-post message tailored for your audience.

AI is crafting your caption…
📘 🐦 💼