πŸ• Restaurant Menu System

Master inheritance through a realistic restaurant menu. Learn to model real-world relationships, override methods, apply the Liskov Substitution Principle, and build polymorphic systems.

Python 3.11+ Hierarchical Inheritance Pricing Overrides Live Code Runner ~60 min In Progress

🎯 By the end of this module, you will be able to:

  • Design a base MenuItem class with shared attributes and methods
  • Create specialized subclasses: Appetizer, MainCourse, Dessert, Beverage
  • Override methods like get_price() and prepare() for unique behavior
  • Use super() correctly to initialize parent classes
  • Apply polymorphism to build flexible menu systems
  • Understand the Liskov Substitution Principle (LSP)
  • Identify when inheritance is (and isn't) the right tool
  • Contrast inheritance with composition using real examples

πŸ“‹ Learning Progress

The Duplication Problem

Without inheritance, you'd write nearly identical classes for each menu category:

❌

Without Inheritance β€” Duplicated Code

class Appetizer: def __init__(self, name, price, description, serving_size): self.name = name self.price = price self.description = description self.serving_size = serving_size def get_price(self): return self.price def prepare(self): return "Preparing appetizer..." class MainCourse: Same __init__, same get_price, same structure class Dessert: Same __init__, same get_price, same structure

⚠️ The Problem: The __init__ and get_price() logic is duplicated. If you need to add a new attribute (like calories), you must update it in three places. This violates the DRY (Don't Repeat Yourself) principle.

The Solution: Inheritance

βœ“

With Inheritance β€” Clean, DRY Code

class MenuItem: def __init__(self, name, price, description): self.name = name self.price = price self.description = description def get_price(self): return self.price def prepare(self): raise NotImplementedError() class Appetizer(MenuItem): def __init__(self, name, price, description, serving_size): super().__init__(name, price, description) self.serving_size = serving_size def prepare(self): return f"Preparing {self.name}..."

βœ… Result: The duplication is gone. All menu items share the same base structure. If you add a new common attribute, you add it once in the base class.

🍽️ MenuItem
β€’ name, price, description
get_price() β†’ price
prepare() [abstract]
display() β†’ formatted string
⬇️ inherits
🍽️ Appetizer
β€’ serving_size
prepare() β†’ plating
🍳 MainCourse
β€’ cooking_time
prepare() β†’ cooking
get_price() β†’ +$2.50
🍰 Dessert
β€’ has_whipped_cream
prepare() β†’ plating
β˜• Beverage
β€’ size, is_hot
prepare() β†’ serving
get_price() β†’ size-based

πŸ”‘ Key Concept: The Open/Closed Principle

The MenuItem base class is open for extension (you can add new menu categories) but closed for modification (you don't need to change existing code). This is one of the SOLID principles of object-oriented design.

The Liskov Substitution Principle (LSP)

LSP states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.

❌ Breaking LSP β€” Bad Example:

class BrokenMainCourse(MainCourse): def get_price(self) -> float: if self.cooking_time > 30: raise ValueError("This dish takes too long!") return super().get_price()

If code expects MainCourse and calls get_price(), it will fail unexpectedly for this subclass. This violates LSP.

βœ… Following LSP β€” Good Example:

class GoodMainCourse(MainCourse): def get_price(self) -> float: if self.cooking_time > 30: return super().get_price() + 5.00 return super().get_price()

This subclass extends behavior without breaking the contract. It still returns a float and works wherever MainCourse is expected.

Inheritance vs Composition

A ComboMeal is not a type of MenuItem β€” it has menu items. This is composition, and it's often the better choice.

Inheritance ("is-a")

"A MainCourse is a MenuItem"

  • Use for specialization
  • Changes to parent affect children
  • Can lead to deep hierarchies

Composition ("has-a")

"A ComboMeal has MenuItem objects"

  • Use for containment
  • Changes are isolated
  • More flexible and testable

πŸ’‘ When to use which: Use inheritance when you have a clear "is-a" relationship and you want to reuse behavior. Use composition when you have a "has-a" relationship or when you want to avoid tight coupling. In modern Python, many developers prefer composition + protocols over deep inheritance.

Build the Restaurant Menu

Edit the code below, then click Run to execute it. Click Test to run automated tests on your code.

restaurant_menu.py

πŸ’‘ Try it yourself: Add a Beverage class, change pricing rules, or add a new method. Click Test to run automated tests on your changes.

Level Up Your Skills

Complete these exercises in order. Each one builds on the previous concepts.

πŸ§ͺ Exercise 1: Add a Beverage Category Easy Not Started

Create a Beverage class that inherits from MenuItem.

  • A size attribute (small, medium, large, extra large)
  • An is_hot boolean attribute
  • A prepare() method that returns a preparation message
  • A display() method that shows the size and temperature
  • Override get_price() to add $0.50 for large and $1.00 for extra large
πŸ’‘ Hint 1: Your class should inherit from MenuItem. Don't forget super().__init__()!
πŸ’‘ Hint 2: In get_price(), check self.size and add the appropriate amount.
πŸ’‘ Hint 3: Use β˜• for hot and 🧊 for iced in display().

πŸ§ͺ Exercise 2: Debug the Menu Medium Not Started

This code has a bug that causes an AttributeError. Find and fix it:

πŸ’‘ Hint 1: The error is in __init__. What does super() do?
πŸ’‘ Hint 2: The parent needs to initialize name, price, and description.
πŸ’‘ Hint 3: Add super().__init__(name, price, description) as the first line of __init__.

πŸ§ͺ Exercise 3: Combo Meal (Composition) Hard Not Started

Create a ComboMeal class that contains multiple MenuItem objects and calculates a discounted total.

This exercise demonstrates composition ("has-a" relationship) rather than inheritance.

πŸ’‘ Hint 1: ComboMeal contains MenuItem objects β€” this is composition.
πŸ’‘ Hint 2: Use sum() with a generator to calculate the total.
πŸ’‘ Hint 3: The get_price() method should apply the discount to the total.

πŸ§ͺ Exercise 4: Fix the LSP Violation Hard Not Started

This class violates the Liskov Substitution Principle. Fix it so it works correctly.

πŸ’‘ Hint 1: LSP says subclasses should not throw exceptions that the parent doesn't.
πŸ’‘ Hint 2: Instead of raising an error, add a surcharge for spicy dishes.
πŸ’‘ Hint 3: Return super().get_price() + 1.50 for spicy dishes.

πŸ§ͺ Exercise 5: Method Resolution Order (MRO) Medium Not Started

What will this code print? Try to work it out, then click "Run" to check.

class A: def whoami(self): return "A" class B(A): def whoami(self): return "B" + super().whoami() class C(A): def whoami(self): return "C" + super().whoami() class D(B, C): def whoami(self): return "D" + super().whoami() print(D().whoami()) print([cls.__name__ for cls in D.__mro__])

Test Your Understanding

Question 1: What does super().__init__() do?

Question 2: Why does the base MenuItem raise NotImplementedError?

Question 3: What concept is demonstrated by calculate_total() working with any menu item?

Question 4: What is the Liskov Substitution Principle?

Question 5: When should you prefer composition over inheritance?

Question 6: What does ClassName.__mro__ show?

What to Avoid

MistakeWhy It's BadBetter Approach
Forgetting super().__init__() Parent attributes are never initialized Always call super().__init__() first
Deep inheritance (5+ levels) Hard to maintain, fragile Prefer shallow hierarchies + composition
Raising exceptions in overridden methods Breaks LSP, violates the contract Extend behavior, don't restrict it
Base class knows about subclasses Violates Open/Closed Principle Keep base class generic
Overriding to change a tiny detail Adds unnecessary complexity Use parameters or composition
Mutable defaults in __init__ Shared state across instances Use None and create fresh objects

Continue Your Learning Journey

πŸ“
Try Other Projects

Zoo Manager, Vehicle Garage, RPG Characters

Explore
πŸ”
Abstract Base Classes

Formal contracts with ABC and @abstractmethod

Coming Soon
🧩
Design Patterns

Strategy, Factory, and more

Coming Soon
πŸ†
Real-World Python

How Django, FastAPI use inheritance

Coming Soon
Back to Inheritance Hub