python

solutions

solutions.py🐍
"""
Solutions for Module 09 - Object-Oriented Programming Exercises

These are reference solutions. Try to solve the exercises yourself first!
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Optional


# =============================================================================
# Basic Classes
# =============================================================================

class Person:
    """A simple Person class demonstrating encapsulation."""
    
    def __init__(self, name: str, age: int):
        self._name = name
        self._age = age
    
    @property
    def name(self) -> str:
        return self._name
    
    @property
    def age(self) -> int:
        return self._age
    
    @age.setter
    def age(self, value: int):
        if value < 0:
            raise ValueError("Age cannot be negative")
        self._age = value
    
    def greet(self) -> str:
        return f"Hello, I'm {self._name} and I'm {self._age} years old."
    
    def __str__(self) -> str:
        return f"Person(name={self._name}, age={self._age})"
    
    def __repr__(self) -> str:
        return f"Person({self._name!r}, {self._age})"


class BankAccount:
    """Bank account demonstrating encapsulation."""
    
    def __init__(self, owner: str, balance: float = 0.0):
        self.owner = owner
        self._balance = balance
        self._transactions: list[tuple[str, float]] = []
    
    @property
    def balance(self) -> float:
        return self._balance
    
    def deposit(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self._balance += amount
        self._transactions.append(("deposit", amount))
    
    def withdraw(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive")
        if amount > self._balance:
            raise ValueError("Insufficient funds")
        self._balance -= amount
        self._transactions.append(("withdraw", amount))
    
    def get_statement(self) -> str:
        lines = [f"Account Statement for {self.owner}"]
        lines.append("-" * 30)
        for tx_type, amount in self._transactions:
            lines.append(f"{tx_type.capitalize()}: ${amount:.2f}")
        lines.append("-" * 30)
        lines.append(f"Balance: ${self._balance:.2f}")
        return "\n".join(lines)


# =============================================================================
# Inheritance
# =============================================================================

class Animal(ABC):
    """Abstract base class for animals."""
    
    def __init__(self, name: str):
        self.name = name
    
    @abstractmethod
    def speak(self) -> str:
        """Return the sound this animal makes."""
        pass
    
    def __str__(self) -> str:
        return f"{self.__class__.__name__}(name={self.name})"


class Dog(Animal):
    """Dog class inheriting from Animal."""
    
    def __init__(self, name: str, breed: str = "Unknown"):
        super().__init__(name)
        self.breed = breed
    
    def speak(self) -> str:
        return f"{self.name} says: Woof!"
    
    def fetch(self) -> str:
        return f"{self.name} fetches the ball!"


class Cat(Animal):
    """Cat class inheriting from Animal."""
    
    def __init__(self, name: str, indoor: bool = True):
        super().__init__(name)
        self.indoor = indoor
    
    def speak(self) -> str:
        return f"{self.name} says: Meow!"
    
    def scratch(self) -> str:
        return f"{self.name} scratches the furniture!"


# =============================================================================
# Polymorphism
# =============================================================================

class Shape(ABC):
    """Abstract base class for shapes."""
    
    @abstractmethod
    def area(self) -> float:
        pass
    
    @abstractmethod
    def perimeter(self) -> float:
        pass


class Rectangle(Shape):
    """Rectangle shape."""
    
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height
    
    def area(self) -> float:
        return self.width * self.height
    
    def perimeter(self) -> float:
        return 2 * (self.width + self.height)


class Circle(Shape):
    """Circle shape."""
    
    def __init__(self, radius: float):
        self.radius = radius
    
    def area(self) -> float:
        import math
        return math.pi * self.radius ** 2
    
    def perimeter(self) -> float:
        import math
        return 2 * math.pi * self.radius


class Triangle(Shape):
    """Triangle shape."""
    
    def __init__(self, a: float, b: float, c: float):
        self.a = a
        self.b = b
        self.c = c
    
    def area(self) -> float:
        # Heron's formula
        s = self.perimeter() / 2
        import math
        return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
    
    def perimeter(self) -> float:
        return self.a + self.b + self.c


def total_area(shapes: list[Shape]) -> float:
    """Calculate total area of all shapes (polymorphism example)."""
    return sum(shape.area() for shape in shapes)


# =============================================================================
# Composition
# =============================================================================

class Engine:
    """Engine component."""
    
    def __init__(self, horsepower: int):
        self.horsepower = horsepower
        self.running = False
    
    def start(self):
        self.running = True
        return "Engine started"
    
    def stop(self):
        self.running = False
        return "Engine stopped"


class Car:
    """Car using composition with Engine."""
    
    def __init__(self, make: str, model: str, horsepower: int):
        self.make = make
        self.model = model
        self.engine = Engine(horsepower)
    
    def start(self) -> str:
        return f"{self.make} {self.model}: {self.engine.start()}"
    
    def stop(self) -> str:
        return f"{self.make} {self.model}: {self.engine.stop()}"
    
    @property
    def is_running(self) -> bool:
        return self.engine.running


# =============================================================================
# Magic Methods (Dunder Methods)
# =============================================================================

class Vector:
    """2D Vector with operator overloading."""
    
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y
    
    def __add__(self, other: "Vector") -> "Vector":
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other: "Vector") -> "Vector":
        return Vector(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar: float) -> "Vector":
        return Vector(self.x * scalar, self.y * scalar)
    
    def __rmul__(self, scalar: float) -> "Vector":
        return self * scalar
    
    def __abs__(self) -> float:
        import math
        return math.sqrt(self.x ** 2 + self.y ** 2)
    
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Vector):
            return NotImplemented
        return self.x == other.x and self.y == other.y
    
    def __repr__(self) -> str:
        return f"Vector({self.x}, {self.y})"
    
    def __str__(self) -> str:
        return f"({self.x}, {self.y})"
    
    def dot(self, other: "Vector") -> float:
        """Dot product."""
        return self.x * other.x + self.y * other.y


# =============================================================================
# Dataclasses
# =============================================================================

@dataclass
class Product:
    """Product using dataclass."""
    name: str
    price: float
    quantity: int = 0
    category: str = "general"
    
    @property
    def total_value(self) -> float:
        return self.price * self.quantity
    
    def is_in_stock(self) -> bool:
        return self.quantity > 0


@dataclass
class Order:
    """Order with multiple products."""
    customer: str
    items: list[Product] = field(default_factory=list)
    
    def add_item(self, product: Product) -> None:
        self.items.append(product)
    
    @property
    def total(self) -> float:
        return sum(item.price * item.quantity for item in self.items)


# =============================================================================
# Class Methods and Static Methods
# =============================================================================

class Temperature:
    """Temperature class with alternative constructors."""
    
    def __init__(self, celsius: float):
        self.celsius = celsius
    
    @classmethod
    def from_fahrenheit(cls, fahrenheit: float) -> "Temperature":
        """Create Temperature from Fahrenheit."""
        celsius = (fahrenheit - 32) * 5 / 9
        return cls(celsius)
    
    @classmethod
    def from_kelvin(cls, kelvin: float) -> "Temperature":
        """Create Temperature from Kelvin."""
        celsius = kelvin - 273.15
        return cls(celsius)
    
    @property
    def fahrenheit(self) -> float:
        return self.celsius * 9 / 5 + 32
    
    @property
    def kelvin(self) -> float:
        return self.celsius + 273.15
    
    @staticmethod
    def is_freezing(temp_celsius: float) -> bool:
        """Check if temperature is at or below freezing."""
        return temp_celsius <= 0
    
    def __repr__(self) -> str:
        return f"Temperature({self.celsius}°C)"


# =============================================================================
# Test Solutions
# =============================================================================

if __name__ == "__main__":
    # Test Person
    p = Person("Alice", 30)
    assert p.greet() == "Hello, I'm Alice and I'm 30 years old."
    
    # Test BankAccount
    acc = BankAccount("Bob", 100)
    acc.deposit(50)
    acc.withdraw(30)
    assert acc.balance == 120
    
    # Test inheritance
    dog = Dog("Buddy", "Labrador")
    cat = Cat("Whiskers")
    assert "Woof" in dog.speak()
    assert "Meow" in cat.speak()
    
    # Test polymorphism
    shapes = [Rectangle(4, 5), Circle(3), Triangle(3, 4, 5)]
    assert total_area(shapes) > 0
    
    # Test composition
    car = Car("Toyota", "Camry", 200)
    car.start()
    assert car.is_running
    
    # Test Vector
    v1 = Vector(1, 2)
    v2 = Vector(3, 4)
    v3 = v1 + v2
    assert v3.x == 4 and v3.y == 6
    
    # Test dataclass
    product = Product("Widget", 9.99, 10)
    assert product.total_value == 99.9
    
    # Test class methods
    t1 = Temperature(20)
    t2 = Temperature.from_fahrenheit(68)
    assert abs(t1.celsius - t2.celsius) < 0.1
    
    print("All OOP solutions verified! ✓")
Solutions - Python Tutorial | DeepML