OOP Fundamentals Competent¶
When you'd use this
Classes, inheritance, polymorphism, encapsulation and abstract base classes.
Model entities with state and behavior — domain objects, plugins, stateful services — and use inheritance/polymorphism to share and specialize logic.
Classes & Objects¶
A class is a blueprint; an object is an instance with its own data. Use classes to bundle related state and behavior — domain entities, services, components.
class Dog:
species = "Canis familiaris" # class variable
def __init__(self, name, age):
self.name = name # instance variable
self.age = age
def bark(self):
return f"{self.name} says Woof!"
rex = Dog("Rex", 5)
print(rex.bark()) # Rex says Woof!
print(Dog.species) # Canis familiaris
Inheritance¶
Derive a subclass that reuses and specializes a base class. Use it for genuine "is-a" relationships; prefer composition when you only want to reuse code.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
super()¶
Call the parent class's implementation — most often to run its __init__ before adding subclass-specific setup.
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
class Car(Vehicle):
def __init__(self, brand, model, doors):
super().__init__(brand, model) # call parent
self.doors = doors
Encapsulation¶
Hide internal state behind methods/properties so callers can't put the object in an invalid state. Use a leading _ by convention and @property for validated access.
class BankAccount:
def __init__(self, balance):
self._balance = balance # convention: "private"
@property
def balance(self):
return self._balance
def deposit(self, amount):
if amount > 0:
self._balance += amount
def withdraw(self, amount):
if 0 < amount <= self._balance:
self._balance -= amount
Polymorphism¶
Different types responding to the same call. Use it to write code against an interface and let each type supply its own behavior — no if type == ... chains.
animals = [Cat("Whiskers"), Dog("Rex")]
for animal in animals:
print(animal.speak()) # each calls its own version
# Whiskers says Meow!
# Rex says Woof!
Abstract Base Classes¶
Define an interface subclasses must implement — instantiating an incomplete subclass fails fast. Use for plugin contracts and enforcing a common API.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
pass
@abstractmethod
def perimeter(self) -> float:
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
Dunder Methods¶
"Double underscore" hooks let your objects work with built-in syntax — +, ==, len(), iteration, repr(). Implement them so your types feel native.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __len__(self):
return int((self.x**2 + self.y**2) ** 0.5)
v = Vector(1, 2) + Vector(3, 4)
print(v) # Vector(4, 6) — __add__ then __repr__
print(Vector(3, 4) == Vector(3, 4)) # True — __eq__
print(len(Vector(3, 4))) # 5 — __len__ (magnitude)
Practice exercises¶
- Build a
Librarysystem withBook,Memberand loan tracking. - Create a shape hierarchy with
Circle,Rectangle,Triangleall implementingarea()andperimeter(). - Implement
__add__,__sub__,__eq__for aMoneyclass with currency conversion. - Write an abstract
Databaseclass with concreteSQLiteDBandPostgresDBimplementations.
💬 Discussion
Have a question about this topic? Found an error? Share your thoughts below.