xbe.at

Python / OOP and design patterns

Comprehensive OOP Concepts and Python Applications

5 min read, 14 sections

Contents
  1. Introduction to Object-Oriented Programming (OOP)
  2. Encapsulation
  3. Polymorphism - Method Overriding
  4. Polymorphism - Method Overloading
  5. Composition
  6. Abstraction
  7. Aggregation
  8. Association
  9. Inheritance
  10. Multiple Inheritance
  11. Method Resolution Order (MRO)
  12. Property Decorators
  13. Static and Class Methods
  14. Additional Resources

Introduction to Object-Oriented Programming (OOP)

Object-Oriented Programming is a programming paradigm that organizes code into objects, which are instances of classes. OOP focuses on the concept of objects that contain data and code, emphasizing the relationships between objects to design and structure programs.

class Car:
    def __init__(self, make, model):
        self.make = make
        self.model = model

    def display_info(self):
        return f"{self.make} {self.model}"

my_car = Car("Toyota", "Corolla")
print(my_car.display_info())

Encapsulation

Encapsulation is the bundling of data and the methods that operate on that data within a single unit (class). It restricts direct access to some of an object's components, which is a means of preventing accidental interference and misuse of the methods and data.

class BankAccount:
    def __init__(self):
        self.__balance = 0

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount

    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount

    def get_balance(self):
        return self.__balance

account = BankAccount()
account.deposit(1000)
print(account.get_balance())

Polymorphism - Method Overriding

Polymorphism allows objects of different classes to be treated as objects of a common base class. Method overriding is a form of polymorphism where a subclass provides a specific implementation of a method that is already defined in its superclass.

class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

def animal_sound(animal):
    return animal.speak()

dog = Dog()
cat = Cat()
print(animal_sound(dog))
print(animal_sound(cat))

Polymorphism - Method Overloading

Python doesn't support method overloading by default, but we can simulate it using default arguments or variable-length arguments.

class Calculator:
    def add(self, *args):
        return sum(args)

calc = Calculator()
print(calc.add(2, 3))
print(calc.add(2, 3, 4))
print(calc.add(2, 3, 4, 5))

Composition

Composition is a design principle that states that classes should achieve polymorphic behavior and code reuse by their composition rather than inheritance from a base or parent class.

class Engine:
    def start(self):
        return "Engine started"

class Car:
    def __init__(self):
        self.engine = Engine()

    def start(self):
        return self.engine.start()

my_car = Car()
print(my_car.start())

Abstraction

Abstraction is the process of hiding the internal details and showing only the functionality. In Python, we can achieve abstraction using abstract base classes.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

square = Square(5)
print(square.area())

Aggregation

Aggregation is a special form of association where objects have a separate lifecycle but share ownership. It represents a "has-a" relationship between objects.

class Department:
    def __init__(self, name):
        self.name = name

class Employee:
    def __init__(self, name, department):
        self.name = name
        self.department = department

dep = Department("IT")
emp1 = Employee("Alice", dep)
emp2 = Employee("Bob", dep)

print(f"{emp1.name} works in {emp1.department.name}")
print(f"{emp2.name} works in {emp2.department.name}")

Association

Association represents a relationship between two or more objects where all objects have their own lifecycle and there is no owner. It can be one-to-one, one-to-many, or many-to-many.

class Student:
    def __init__(self, name):
        self.name = name
        self.courses = []

    def enroll(self, course):
        self.courses.append(course)
        course.add_student(self)

class Course:
    def __init__(self, name):
        self.name = name
        self.students = []

    def add_student(self, student):
        self.students.append(student)

student1 = Student("Alice")
course1 = Course("Python Programming")
student1.enroll(course1)

print(f"{student1.name} is enrolled in {student1.courses[0].name}")
print(f"{course1.name} has {course1.students[0].name} as a student")

Inheritance

Inheritance is a mechanism where a class can derive properties and characteristics from another class. It supports the concept of hierarchical classification and code reuse.

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

dog = Dog("Buddy")
cat = Cat("Whiskers")

print(dog.speak())
print(cat.speak())

Multiple Inheritance

Python supports multiple inheritance, allowing a class to inherit from multiple parent classes. This can lead to powerful combinations of behaviors but should be used carefully to avoid complexity.

class Flyer:
    def fly(self):
        return "I can fly!"

class Swimmer:
    def swim(self):
        return "I can swim!"

class Duck(Flyer, Swimmer):
    pass

duck = Duck()
print(duck.fly())
print(duck.swim())

Method Resolution Order (MRO)

When using multiple inheritance, Python uses the C3 Linearization algorithm to determine the order in which base classes are searched when looking for a method. This is known as the Method Resolution Order (MRO).

class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

class C(A):
    def method(self):
        return "C"

class D(B, C):
    pass

d = D()
print(d.method())
print(D.mro())

Property Decorators

Property decorators provide a way to customize access to class attributes, allowing you to define methods that can be accessed like attributes.

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero is not possible")
        self._celsius = value

    @property
    def fahrenheit(self):
        return (self.celsius * 9/5) + 32

temp = Temperature(25)
print(f"Celsius: {temp.celsius}")
print(f"Fahrenheit: {temp.fahrenheit}")

temp.celsius = 30
print(f"New Celsius: {temp.celsius}")
print(f"New Fahrenheit: {temp.fahrenheit}")

Static and Class Methods

Static methods and class methods are special methods that can be called on a class without creating an instance of the class. They serve different purposes and have different behaviors.

class MathOperations:
    @staticmethod
    def add(x, y):
        return x + y

    @classmethod
    def multiply(cls, x, y):
        return cls.add(x, 0) * y

print(MathOperations.add(5, 3))
print(MathOperations.multiply(4, 2))

Additional Resources

For further exploration of Object-Oriented Programming concepts and their implementation in Python, consider the following resources:

  1. "Object-Oriented Programming in Python: A Comprehensive Study" by Bhargavi et al. (2023) ArXiv URL: https://arxiv.org/abs/2308.15827
  2. "Python Design Patterns" by Chetan Giridhar (Book)
  3. "Fluent Python" by Luciano Ramalho (Book)
  4. Python's official documentation on classes: https://docs.python.org/3/tutorial/classes.html

These resources provide in-depth explanations and advanced techniques for mastering OOP in Python.

More in OOP and design patterns

  1. Data Classes vs. Named Tuples in Python11 min

    Data Classes and Named Tuples are two powerful tools in Python for organizing and structuring data. While they serve similar purposes, they have distinct...

  2. Behavior Trees and Hierarchical State Machines in Python6 min

    Trees and Hierarchical State Machines In this slideshow, we'll explore two powerful techniques for modeling and implementing complex behaviors in Python...

  3. Decorating Local Methods in Python6 min

    Local decorators in Python allow us to create function wrappers within a specific scope, typically inside a class. This provides better encapsulation and...

  4. 20 Essential Python OOP Magic Methods7 min

    The init method initializes a new instance of a class, setting up the initial state by assigning values to instance attributes. It's automatically called...

  5. Decorating Python Classes4 min

    Class decorators are a powerful feature in Python that allow you to modify or enhance the behavior of classes. They are similar to function decorators but...

  6. Dependency Inversion Principle in Python:4 min

    The Dependency Inversion Principle (DIP) is one of the core principles of the SOLID principles in object-oriented programming. It states that high-level...