Inheritance allows new classes to be based on existing classes, reusing code and creating hierarchical relationships. Polymorphism allows objects of different classes to be treated as objects of a common superclass. Abstraction hides complex implementation details and shows only essential features. Encapsulation bundles data and methods together, protecting data from direct access.
# Object-Oriented Programming - Part 2
print("OBJECT-ORIENTED PROGRAMMING (OOP) - PART 2")
print("=" * 60)
# Example 1: Basic Inheritance
print("\n1. BASIC INHERITANCE")
print("-" * 30)
class Animal:
"""Base class for all animals"""
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
return f"{self.name} is eating."
def sleep(self):
return f"{self.name} is sleeping."
def make_sound(self):
return "Some generic animal sound"
class Dog(Animal): # Dog inherits from Animal
"""Dog class - extends Animal"""
def __init__(self, name, age, breed):
# Call parent class constructor
super().__init__(name, age)
self.breed = breed
# Override parent method
def make_sound(self):
return "Woof! Woof!"
# Add new method specific to Dog
def fetch(self):
return f"{self.name} is fetching the ball!"
class Cat(Animal): # Cat inherits from Animal
"""Cat class - extends Animal"""
def __init__(self, name, age, color):
super().__init__(name, age)
self.color = color
# Override parent method
def make_sound(self):
return "Meow!"
# Add new method specific to Cat
def climb(self):
return f"{self.name} is climbing a tree!"
# Create animals
generic_animal = Animal("Generic", 1)
dog = Dog("Buddy", 3, "Golden Retriever")
cat = Cat("Whiskers", 2, "Orange")
print("Animal behaviors:")
print(f"Generic: {generic_animal.make_sound()}")
print(f"Dog: {dog.make_sound()}")
print(f"Cat: {cat.make_sound()}")
print("\nCommon behaviors (inherited):")
print(dog.eat())
print(cat.sleep())
print("\nSpecial behaviors (specific to subclass):")
print(dog.fetch())
print(cat.climb())
print("\nAnimal information:")
print(f"Dog: {dog.name}, {dog.age} years old, {dog.breed}")
print(f"Cat: {cat.name}, {cat.age} years old, {cat.color}")
# Example 2: Multi-level Inheritance
print("\n\n2. MULTI-LEVEL INHERITANCE")
print("-" * 30)
class Vehicle:
"""Base class for all vehicles"""
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
self.speed = 0
def start(self):
return f"{self.brand} {self.model} started."
def stop(self):
self.speed = 0
return f"{self.brand} {self.model} stopped."
def accelerate(self, increment):
self.speed += increment
return f"Accelerating to {self.speed} km/h"
class Car(Vehicle):
"""Car class - extends Vehicle"""
def __init__(self, brand, model, year, doors):
super().__init__(brand, model, year)
self.doors = doors
def honk(self):
return "Beep! Beep!"
class ElectricCar(Car):
"""Electric Car - extends Car"""
def __init__(self, brand, model, year, doors, battery_capacity):
super().__init__(brand, model, year, doors)
self.battery_capacity = battery_capacity
self.battery_level = 100
def charge(self, amount):
"""Charge the battery"""
self.battery_level = min(100, self.battery_level + amount)
return f"Battery: {self.battery_level}%"
def drive(self, distance):
"""Drive and consume battery"""
battery_used = distance * 0.5 # 0.5% per km
if battery_used <= self.battery_level:
self.battery_level -= battery_used
return f"Drove {distance}km. Battery: {self.battery_level:.1f}%"
return "Not enough battery!"
# Create vehicles
regular_car = Car("Toyota", "Camry", 2022, 4)
tesla = ElectricCar("Tesla", "Model 3", 2023, 4, 75)
print("Regular Car:")
print(regular_car.start())
print(regular_car.accelerate(50))
print(regular_car.honk())
print("\nElectric Car:")
print(tesla.start())
print(tesla.drive(50))
print(tesla.charge(25))
# Check inheritance
print("\nChecking inheritance hierarchy:")
print(f"Is tesla an ElectricCar? {isinstance(tesla, ElectricCar)}")
print(f"Is tesla a Car? {isinstance(tesla, Car)}")
print(f"Is tesla a Vehicle? {isinstance(tesla, Vehicle)}")
print(f"Is ElectricCar a subclass of Vehicle? {issubclass(ElectricCar, Vehicle)}")
# Example 3: Multiple Inheritance
print("\n\n3. MULTIPLE INHERITANCE")
print("-" * 30)
class Camera:
"""Camera functionality"""
def __init__(self, resolution):
self.resolution = resolution
def take_photo(self):
return f"Taking photo at {self.resolution} resolution"
def zoom(self, factor):
return f"Zooming {factor}x"
class Phone:
"""Phone functionality"""
def __init__(self, number):
self.number = number
def call(self, contact):
return f"Calling {contact} from {self.number}"
def send_sms(self, contact, message):
return f"Sending SMS to {contact}: {message}"
class SmartPhone(Camera, Phone): # Inherits from both
"""SmartPhone - combines Camera and Phone"""
def __init__(self, number, resolution, model):
# Initialize both parent classes
Camera.__init__(self, resolution)
Phone.__init__(self, number)
self.model = model
def browse_internet(self):
return f"Browsing internet on {self.model}"
# Create smartphone
iphone = SmartPhone("123-456-7890", "12MP", "iPhone 15")
print("Smartphone features:")
print(iphone.take_photo())
print(iphone.zoom(2))
print(iphone.call("Mom"))
print(iphone.send_sms("Friend", "Hello!"))
print(iphone.browse_internet())
# Check Method Resolution Order (MRO)
print("\nMethod Resolution Order:")
for i, cls in enumerate(SmartPhone.__mro__, 1):
print(f"{i}. {cls.__name__}")
# Example 4: Abstract Base Classes
print("\n\n4. ABSTRACT BASE CLASSES")
print("-" * 30)
from abc import ABC, abstractmethod
class Shape(ABC): # Abstract Base Class
"""Abstract base class for shapes"""
@abstractmethod
def area(self):
"""Calculate area - must be implemented by subclasses"""
pass
@abstractmethod
def perimeter(self):
"""Calculate perimeter - must be implemented by subclasses"""
pass
def display_info(self):
"""Common method for all shapes"""
return f"Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}"
class Circle(Shape):
"""Circle class implementing Shape"""
def __init__(self, radius):
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
def perimeter(self):
import math
return 2 * math.pi * self.radius
class Rectangle(Shape):
"""Rectangle class implementing Shape"""
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)
# Cannot instantiate abstract class
# shape = Shape() # This would raise TypeError
# Create concrete shapes
circle = Circle(5)
rectangle = Rectangle(4, 6)
print("Shape calculations:")
print(f"Circle with radius 5:")
print(circle.display_info())
print(f"\nRectangle 4x6:")
print(rectangle.display_info())
# Example 5: Polymorphism in action
print("\n\n5. POLYMORPHISM IN ACTION")
print("-" * 30)
class Employee(ABC):
"""Abstract base employee class"""
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
@abstractmethod
def calculate_salary(self):
pass
def get_info(self):
return f"{self.name} (ID: {self.employee_id})"
class FullTimeEmployee(Employee):
"""Full-time employee with monthly salary"""
def __init__(self, name, employee_id, monthly_salary):
super().__init__(name, employee_id)
self.monthly_salary = monthly_salary
def calculate_salary(self):
return self.monthly_salary
class PartTimeEmployee(Employee):
"""Part-time employee with hourly wage"""
def __init__(self, name, employee_id, hourly_wage, hours_worked):
super().__init__(name, employee_id)
self.hourly_wage = hourly_wage
self.hours_worked = hours_worked
def calculate_salary(self):
return self.hourly_wage * self.hours_worked
class Contractor(Employee):
"""Contractor with project-based payment"""
def __init__(self, name, employee_id, project_fee, completed_projects):
super().__init__(name, employee_id)
self.project_fee = project_fee
self.completed_projects = completed_projects
def calculate_salary(self):
return self.project_fee * self.completed_projects
# Create different types of employees
employees = [
FullTimeEmployee("Alice", "FT001", 5000),
PartTimeEmployee("Bob", "PT001", 20, 80),
Contractor("Charlie", "CT001", 1000, 3)
]
# Process all employees polymorphically
print("Employee Salaries:")
print("-" * 40)
total_payroll = 0
for employee in employees:
salary = employee.calculate_salary()
total_payroll += salary
print(f"{employee.get_info():30} Salary: ${salary:,.2f}")
print("-" * 40)
print(f"Total Payroll:{'':22} ${total_payroll:,.2f}")
# Example 6: Composition vs Inheritance
print("\n\n6. COMPOSITION VS INHERITANCE")
print("-" * 30)
# Composition example
class Engine:
def __init__(self, horsepower):
self.horsepower = horsepower
def start(self):
return "Engine started"
def stop(self):
return "Engine stopped"
class Wheels:
def __init__(self, count):
self.count = count
def rotate(self):
return f"{self.count} wheels rotating"
# Using composition (has-a relationship)
class CarComposition:
def __init__(self, brand, engine_hp, wheel_count):
self.brand = brand
self.engine = Engine(engine_hp) # Car HAS an Engine
self.wheels = Wheels(wheel_count) # Car HAS Wheels
def drive(self):
return f"{self.brand}: {self.engine.start()}, {self.wheels.rotate()}"
# Create car using composition
my_car = CarComposition("Toyota", 150, 4)
print("Car using composition:")
print(my_car.drive())
print(f"Engine HP: {my_car.engine.horsepower}")
print(f"Wheel count: {my_car.wheels.count}")
print("\nWhen to use each:")
print("Inheritance (is-a): A Dog IS AN Animal")
print("Composition (has-a): A Car HAS AN Engine")