🏗️ Object-Oriented Programming

Polymorphism & Interfaces

What is Polymorphism?

"One interface, many implementations."

from abc import ABC, abstractmethod
from math import pi

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

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        return pi * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    def area(self) -> float:
        return self.width * self.height

class Triangle(Shape):
    def __init__(self, base: float, height: float):
        self.base = base
        self.height = height

    def area(self) -> float:
        return 0.5 * self.base * self.height

def process(shape: Shape) -> None:
    print(f"Area: {shape.area():.2f}")

process(Circle(5))        # 78.54
process(Rectangle(3, 4))  # 12
process(Triangle(3, 4))   # 6
#include <iostream>
#include <cmath>

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

class Circle : public Shape {
    double radius_;
public:
    explicit Circle(double r) : radius_(r) {}
    double area() const override { return M_PI * radius_ * radius_; }
};

class Rectangle : public Shape {
    double width_, height_;
public:
    Rectangle(double w, double h) : width_(w), height_(h) {}
    double area() const override { return width_ * height_; }
};

class Triangle : public Shape {
    double base_, height_;
public:
    Triangle(double b, double h) : base_(b), height_(h) {}
    double area() const override { return 0.5 * base_ * height_; }
};

void process(const Shape& shape) {
    std::cout << "Area: " << shape.area() << std::endl;
}

int main() {
    process(Circle(5));        // 78.54
    process(Rectangle(3, 4));  // 12
    process(Triangle(3, 4));   // 6
}
public abstract class Shape {
    public abstract double area();
}

public final class Circle extends Shape {
    private final double radius;
    public Circle(double r) { this.radius = r; }
    public double area() { return Math.PI * radius * radius; }
}

public final class Rectangle extends Shape {
    private final double width, height;
    public Rectangle(double w, double h) { this.width = w; this.height = h; }
    public double area() { return width * height; }
}

public final class Triangle extends Shape {
    private final double base, height;
    public Triangle(double b, double h) { this.base = b; this.height = h; }
    public double area() { return 0.5 * base * height; }
}

public final class Example {
    public static void process(Shape shape) {
        System.out.println("Area: " + shape.area());
    }

    public static void main(String[] args) {
        process(new Circle(5));        // 78.54
        process(new Rectangle(3, 4));  // 12
        process(new Triangle(3, 4));   // 6
    }
}
using System;

public abstract class Shape {
    public abstract double Area();
}

public sealed class Circle : Shape {
    private readonly double _radius;
    public Circle(double r) => _radius = r;
    public override double Area() => Math.PI * _radius * _radius;
}

public sealed class Rectangle : Shape {
    private readonly double _width, _height;
    public Rectangle(double w, double h) { _width = w; _height = h; }
    public override double Area() => _width * _height;
}

public sealed class Triangle : Shape {
    private readonly double _base, _height;
    public Triangle(double b, double h) { _base = b; _height = h; }
    public override double Area() => 0.5 * _base * _height;
}

public static class Program {
    static void Process(Shape shape) {
        Console.WriteLine($"Area: {shape.Area():F2}");
    }

    static void Main() {
        Process(new Circle(5));        // 78.54
        Process(new Rectangle(3, 4));  // 12
        Process(new Triangle(3, 4));   // 6
    }
}

Types of Polymorphism

Type Example
Subtype (Runtime) Inheritance + method overriding
Parametric (Generics) List[T], Optional[T]
Ad-hoc (Overloading) Multiple add(a, b) for different types

Subtype Polymorphism (The Classic)

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def charge(self, amount: float) -> bool:
        pass

class StripeProcessor(PaymentProcessor):
    def charge(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} via Stripe")
        return True

class PayPalProcessor(PaymentProcessor):
    def charge(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} via PayPal")
        return True

class CryptoProcessor(PaymentProcessor):
    def charge(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} via Crypto")
        return True
class PaymentProcessor {
public:
    virtual bool charge(double amount) = 0;
    virtual ~PaymentProcessor() = default;
};

class StripeProcessor : public PaymentProcessor {
public:
    bool charge(double amount) override {
        std::cout << "Charging $" << amount << " via Stripe" << std::endl;
        return true;
    }
};

class PayPalProcessor : public PaymentProcessor {
public:
    bool charge(double amount) override {
        std::cout << "Charging $" << amount << " via PayPal" << std::endl;
        return true;
    }
};

class CryptoProcessor : public PaymentProcessor {
public:
    bool charge(double amount) override {
        std::cout << "Charging $" << amount << " via Crypto" << std::endl;
        return true;
    }
};
public interface PaymentProcessor {
    boolean charge(double amount);
}

public final class StripeProcessor implements PaymentProcessor {
    public boolean charge(double amount) {
        System.out.println("Charging $" + amount + " via Stripe");
        return true;
    }
}

public final class PayPalProcessor implements PaymentProcessor {
    public boolean charge(double amount) {
        System.out.println("Charging $" + amount + " via PayPal");
        return true;
    }
}

public final class CryptoProcessor implements PaymentProcessor {
    public boolean charge(double amount) {
        System.out.println("Charging $" + amount + " via Crypto");
        return true;
    }
}
public interface IPaymentProcessor {
    bool Charge(double amount);
}

public sealed class StripeProcessor : IPaymentProcessor {
    public bool Charge(double amount) {
        Console.WriteLine($"Charging ${amount:F2} via Stripe");
        return true;
    }
}

public sealed class PayPalProcessor : IPaymentProcessor {
    public bool Charge(double amount) {
        Console.WriteLine($"Charging ${amount:F2} via PayPal");
        return true;
    }
}

public sealed class CryptoProcessor : IPaymentProcessor {
    public bool Charge(double amount) {
        Console.WriteLine($"Charging ${amount:F2} via Crypto");
        return true;
    }
}
def checkout(processor: PaymentProcessor, amount: float) -> None:
    if processor.charge(amount):
        print("Payment successful!")

if __name__ == "__main__":
    checkout(StripeProcessor(), 99.99)
    checkout(PayPalProcessor(), 49.99)
    checkout(CryptoProcessor(), 199.00)
void checkout(PaymentProcessor& processor, double amount) {
    if (processor.charge(amount)) {
        std::cout << "Payment successful!\n";
    }
}

int main() {
    checkout(StripeProcessor(), 99.99);
    checkout(PayPalProcessor(), 49.99);
    checkout(CryptoProcessor(), 199.00);
}
public static void checkout(PaymentProcessor processor, double amount) {
    if (processor.charge(amount)) {
        System.out.println("Payment successful!");
    }
}

public static void main(String[] args) {
    checkout(new StripeProcessor(), 99.99);
    checkout(new PayPalProcessor(), 49.99);
    checkout(new CryptoProcessor(), 199.00);
}
static void Checkout(IPaymentProcessor processor, double amount) {
    if (processor.Charge(amount)) {
        Console.WriteLine("Payment successful!");
    }
}

static void Main() {
    Checkout(new StripeProcessor(), 99.99);
    Checkout(new PayPalProcessor(), 49.99);
    Checkout(new CryptoProcessor(), 199.00);
}

Interface Segregation Principle

Many specific interfaces > one general interface

from abc import ABC, abstractmethod

# BAD — fat interface
class Worker(ABC):
    @abstractmethod
    def work(self) -> None: pass
    @abstractmethod
    def eat(self) -> None: pass
    @abstractmethod
    def sleep(self) -> None: pass

class Robot(Worker):
    def work(self) -> None: pass
    def eat(self) -> None: raise RuntimeError("Robots don't eat!")
    def sleep(self) -> None: raise RuntimeError("Robots don't sleep!")

# GOOD — segregated interfaces
class Workable(ABC):
    @abstractmethod
    def work(self) -> None: pass

class Eatable(ABC):
    @abstractmethod
    def eat(self) -> None: pass

class Sleepable(ABC):
    @abstractmethod
    def sleep(self) -> None: pass

class Human(Workable, Eatable, Sleepable):
    def work(self) -> None: pass
    def eat(self) -> None: pass
    def sleep(self) -> None: pass
// BAD — fat interface
class Worker {
public:
    virtual void work() = 0;
    virtual void eat() = 0;
    virtual void sleep() = 0;
    virtual ~Worker() = default;
};

class Robot : public Worker {
public:
    void work() override {}
    void eat() override { throw std::runtime_error("Robots don't eat!"); }
    void sleep() override { throw std::runtime_error("Robots don't sleep!"); }
};

// GOOD — segregated interfaces
class Workable { public: virtual void work() = 0; virtual ~Workable() = default; };
class Eatable  { public: virtual void eat() = 0;  virtual ~Eatable() = default; };
class Sleepable{ public: virtual void sleep() = 0; virtual ~Sleepable() = default; };

class Human : public Workable, public Eatable, public Sleepable {
public:
    void work() override {}
    void eat() override {}
    void sleep() override {}
};
// BAD — fat interface
interface Worker {
    void work();
    void eat();
    void sleep();
}

class Robot implements Worker {
    public void work() {}
    public void eat() { throw new RuntimeException("Robots don't eat!"); }
    public void sleep() { throw new RuntimeException("Robots don't sleep!"); }
}

// GOOD — segregated interfaces
interface Workable { void work(); }
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }

class Human implements Workable, Eatable, Sleepable {
    public void work() {}
    public void eat() {}
    public void sleep() {}
}
// BAD — fat interface
interface IWorker {
    void Work();
    void Eat();
    void Sleep();
}

class Robot : IWorker {
    public void Work() {}
    public void Eat() => throw new Exception("Robots don't eat!");
    public void Sleep() => throw new Exception("Robots don't sleep!");
}

// GOOD — segregated interfaces
interface IWorkable { void Work(); }
interface IEatable { void Eat(); }
interface ISleepable { void Sleep(); }

class Human : IWorkable, IEatable, ISleepable {
    public void Work() {}
    public void Eat() {}
    public void Sleep() {}
}

Abstract Base Classes (ABC)

from abc import ABC, abstractmethod
from typing import Callable

class Database(ABC):
    @abstractmethod
    def connect(self) -> None:
        pass

    @abstractmethod
    def execute(self, query: str) -> None:
        pass

    @abstractmethod
    def commit(self) -> None:
        pass

    @abstractmethod
    def rollback(self) -> None:
        pass

    # Concrete method (template method pattern)
    def transaction(self, work: Callable[[], None]) -> None:
        self.connect()
        try:
            work()
            self.commit()
        except Exception:
            self.rollback()
            raise
class Database {
public:
    virtual ~Database() = default;
    virtual void connect() = 0;
    virtual void execute(const std::string& query) = 0;
    virtual void commit() = 0;
    virtual void rollback() = 0;

    // Concrete method (template method pattern)
    template<typename F>
    void transaction(std::function<void()> work) {
        connect();
        try {
            work();
            commit();
        } catch (...) {
            rollback();
            throw;
        }
    }
};
public abstract class Database {
    public abstract void connect();
    public abstract void execute(String query);
    public abstract void commit();
    public abstract void rollback();

    public void transaction(Runnable work) {
        connect();
        try {
            work.run();
            commit();
        } catch (Exception e) {
            rollback();
            throw new RuntimeException(e);
        }
    }
}
public abstract class Database {
    public abstract void Connect();
    public abstract void Execute(string query);
    public abstract void Commit();
    public abstract void Rollback();

    public void Transaction(Action work) {
        Connect();
        try {
            work();
            Commit();
        } catch {
            Rollback();
            throw;
        }
    }
}

Mixing Abstract + Concrete

from abc import ABC, abstractmethod

class HTTPClient(ABC):
    @abstractmethod
    def request(self, method: str, url: str, data: str = "") -> str:
        pass

    def get(self, url: str) -> str:
        return self.request("GET", url)

    def post(self, url: str, data: str = "") -> str:
        return self.request("POST", url, data)

    def put(self, url: str, data: str = "") -> str:
        return self.request("PUT", url, data)

    def delete(self, url: str) -> str:
        return self.request("DELETE", url)
class HTTPClient {
public:
    virtual ~HTTPClient() = default;
    virtual std::string request(std::string method, std::string url, std::string data = "") = 0;

    std::string get(std::string url) { return request("GET", url, ""); }
    std::string post(std::string url, std::string data = "") { return request("POST", url, data); }
    std::string put(std::string url, std::string data = "") { return request("PUT", url, data); }
    std::string del(std::string url) { return request("DELETE", url, ""); }
};
public abstract class HTTPClient {
    public abstract String request(String method, String url, String data);

    public String get(String url) { return request("GET", url, ""); }
    public String post(String url, String data) { return request("POST", url, data); }
    public String put(String url, String data) { return request("PUT", url, data); }
    public String delete(String url) { return request("DELETE", url, ""); }
}
public abstract class HttpClient {
    public abstract string Request(string method, string url, string data = "");

    public string Get(string url) => Request("GET", url);
    public string Post(string url, string data) => Request("POST", url, data);
    public string Put(string url, string data) => Request("PUT", url, data);
    public string Delete(string url) => Request("DELETE", url);
}

Mixing Abstract + Concrete (Template Method Pattern)

from abc import ABC, abstractmethod
from typing import Callable, List

class DataExporter(ABC):
    @abstractmethod
    def write_header(self) -> None:
        pass

    @abstractmethod
    def write_row(self, data: dict) -> None:
        pass

    @abstractmethod
    def write_footer(self) -> None:
        pass

    # Concrete template method
    def export(self, data: List[dict]) -> None:
        self.write_header()
        for row in data:
            self.write_row(row)
        self.write_footer()

class CSVExporter(DataExporter):
    def write_header(self) -> None:
        print("id,name,email")

    def write_row(self, data: dict) -> None:
        print(f"{data['id']},{data['name']},{data['email']}")

    def write_footer(self) -> None:
        print("# End of CSV")

class JSONExporter(DataExporter):
    def write_header(self) -> None:
        print("[")

    def write_row(self, data: dict) -> None:
        print(f'  {{"id": {data["id"]}, "name": "{data["name"]}", "email": "{data["email"]}"}},')

    def write_footer(self) -> None:
        print("]")
#include <vector>
#include <string>
#include <iostream>

class DataExporter {
public:
    virtual ~DataExporter() = default;
    virtual void writeHeader() = 0;
    virtual void writeRow(const std::string& data) = 0;
    virtual void writeFooter() = 0;

    // Concrete template method
    void export(const std::vector<std::string>& data) {
        writeHeader();
        for (const auto& row : data) writeRow(row);
        writeFooter();
    }
};

class CSVExporter : public DataExporter {
    void writeHeader() override { std::cout << "id,name,email\n"; }
    void writeRow(const std::string& data) override { std::cout << data << "\n"; }
    void writeFooter() override { std::cout << "# End of CSV\n"; }
};

class JSONExporter : public DataExporter {
    void writeHeader() override { std::cout << "[\n"; }
    void writeRow(const std::string& data) override { std::cout << "  " << data << ",\n"; }
    void writeFooter() override { std::cout << "]\n"; }
};
public abstract class DataExporter {
    public abstract void writeHeader();
    public abstract void writeRow(String data);
    public abstract void writeFooter();

    // Concrete template method
    public void export(List<String> data) {
        writeHeader();
        for (String row : data) writeRow(row);
        writeFooter();
    }
}

class CSVExporter extends DataExporter {
    public void writeHeader() { System.out.println("id,name,email"); }
    public void writeRow(String data) { System.out.println(data); }
    public void writeFooter() { System.out.println("# End of CSV"); }
}

class JSONExporter extends DataExporter {
    public void writeHeader() { System.out.print("[\n"); }
    public void writeRow(String data) { System.out.println("  " + data + ","); }
    public void writeFooter() { System.out.println("]"); }
}
public abstract class DataExporter {
    public abstract void WriteHeader();
    public abstract void WriteRow(string data);
    public abstract void WriteFooter();

    // Concrete template method
    public void Export(IEnumerable<string> data) {
        WriteHeader();
        foreach (var row in data) WriteRow(row);
        WriteFooter();
    }
}

public sealed class CSVExporter : DataExporter {
    public override void WriteHeader() => Console.WriteLine("id,name,email");
    public override void WriteRow(string data) => Console.WriteLine(data);
    public override void WriteFooter() => Console.WriteLine("# End of CSV");
}

public sealed class JSONExporter : DataExporter {
    public override void WriteHeader() => Console.WriteLine("[");
    public override void WriteRow(string data) => Console.WriteLine("  " + data + ",");
    public override void WriteFooter() => Console.WriteLine("]");
}

Multiple Dispatch / Double Dispatch

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def collide_with(self, other: 'Shape') -> str:
        pass

    def collide(self, other: 'Shape') -> str:
        return other.collide_with(self)

class Circle(Shape):
    def collide_with(self, other: 'Shape') -> str:
        if isinstance(other, Circle):
            return "Circle-Circle"
        return "Circle-Other"

class Rectangle(Shape):
    def collide_with(self, other: 'Shape') -> str:
        if isinstance(other, Rectangle):
            return "Rect-Rect"
        return "Rect-Other"

# Usage
circle = Circle()
rect = Rectangle()
print(circle.collide(rect))  # Circle-Rect
print(rect.collide(circle))  # Circle-Rect (double dispatch!)
#include <memory>
#include <string>

class Shape {
public:
    virtual ~Shape() = default;
    virtual std::string collideWith(const Shape& other) const = 0;
    std::string collide(const Shape& other) const { return other.collideWith(*this); }
};

class Circle : public Shape {
    std::string collideWith(const Shape& other) const override {
        if (dynamic_cast<const Circle*>(&other)) return "Circle-Circle";
        return "Circle-Other";
    }
};

class Rectangle : public Shape {
    std::string collideWith(const Shape& other) const override {
        if (dynamic_cast<const Rectangle*>(&other)) return "Rect-Rect";
        return "Rect-Other";
    }
};

// Usage
Circle circle; Rectangle rect;
circle.collide(rect);  // Circle-Rect
rect.collide(circle);  // Circle-Rect (double dispatch!)
interface Shape {
    String collideWith(Shape other);
    default String collide(Shape other) { return other.collideWith(this); }
}

class Circle implements Shape {
    public String collideWith(Shape other) {
        if (other instanceof Circle) return "Circle-Circle";
        return "Circle-Other";
    }
}

class Rectangle implements Shape {
    public String collideWith(Shape other) {
        if (other instanceof Rectangle) return "Rect-Rect";
        return "Rect-Other";
    }
}

// Usage
Shape circle = new Circle();
Shape rect = new Rectangle();
circle.collide(rect);  // Circle-Rect
rect.collide(circle);  // Circle-Rect (double dispatch!)
public interface IShape {
    string CollideWith(IShape other);
    string Collide(IShape other) => other.CollideWith(this);
}

public sealed class Circle : IShape {
    public string CollideWith(IShape other) => other is Circle ? "Circle-Circle" : "Circle-Other";
}

public sealed class Rectangle : IShape {
    public string CollideWith(IShape other) => other is Rectangle ? "Rect-Rect" : "Rect-Other";
}

// Usage
var circle = new Circle();
var rect = new Rectangle();
circle.Collide(rect);  // Circle-Rect
rect.Collide(circle);  // Circle-Rect (double dispatch!)

Type Classes / Protocols (Ad-hoc Polymorphism)

from typing import Protocol, TypeVar
T = TypeVar('T')

class Comparable(Protocol):
    def __lt__(self, other: T) -> bool: ...

def sort(items: list[T]) -> list[T]:
    return sorted(items)

# Any type implementing __lt__ works
print(sort([3, 1, 2]))
print(sort(["c", "a", "b"]))
#include <algorithm>
#include <vector>
#include <string>

template<typename T>
requires std::sortable<T>
std::vector<T> sort(std::vector<T> items) {
    std::ranges::sort(items);
    return items;
}

// Any type with operator< works
std::vector<int> v1 = {3, 1, 2};
std::vector<std::string> v2 = {"c", "a", "b"};
auto s1 = sort(v1);
auto s2 = sort(v2);
import java.util.*;

public class Sorter {
    public static <T extends Comparable<T>> List<T> sort(List<T> items) {
        var copy = new ArrayList<>(items);
        copy.sort(Comparator.naturalOrder());
        return copy;
    }

    public static void main(String[] args) {
        System.out.println(sort(List.of(3, 1, 2)));
        System.out.println(sort(List.of("c", "a", "b")));
    }
}
using System;
using System.Collections.Generic;
using System.Linq;

public static class Sorter {
    public static List<T> Sort<T>(List<T> items) where T : IComparable<T> {
        var copy = items.ToList();
        copy.Sort();
        return copy;
    }

    static void Main() {
        Console.WriteLine(string.Join(", ", Sort(new List<int> { 3, 1, 2 })));
        Console.WriteLine(string.Join(", ", Sort(new List<string> { "c", "a", "b" })));
    }
}