Bài 1.2: Advanced OOP - Abstract Classes & Metaclasses (Phần 2)

Mục Tiêu Bài Học

Sau khi hoàn thành bài này, bạn sẽ:

  • ✅ Tạo abstract classes với ABC
  • ✅ Define interfaces trong Python
  • ✅ Hiểu multiple inheritance
  • ✅ Master Method Resolution Order (MRO)
  • ✅ Sử dụng mixins
  • ✅ Hiểu metaclasses cơ bản

Abstract Base Classes (ABC)

Abstract classes là classes không thể instantiate, chỉ dùng làm base class.

Basic Abstract Class

from abc import ABC, abstractmethod class Shape(ABC):    """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 describe(self):        """Concrete method - inherited by subclasses."""        return f"Shape with area {self.area()} and perimeter {self.perimeter()}" class Rectangle(Shape):    """Concrete implementation of Shape."""        def __init__(self, width, height):        self.width = width        self.height = height        def area(self):        return self.width * self.height        def perimeter(self):        return 2 * (self.width + self.height) class Circle(Shape):    """Another concrete implementation."""        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 # Usagerect = Rectangle(5, 10)print(rect.area())        # 50print(rect.describe())    # Shape with area 50 and perimeter 30 circle = Circle(7)print(circle.area())      # 153.94 # Cannot instantiate abstract class# shape = Shape()  # TypeError: Can't instantiate abstract class

Abstract Properties

from abc import ABC, abstractmethod class Animal(ABC):    """Abstract animal class."""        def __init__(self, name):        self.name = name        @property    @abstractmethod    def sound(self):        """Abstract property - must be implemented."""        pass        @property    @abstractmethod    def legs(self):        """Another abstract property."""        pass        def make_sound(self):        """Concrete method using abstract property."""        return f"{self.name} says {self.sound}" class Dog(Animal):    """Concrete dog class."""        @property    def sound(self):        return "Woof!"        @property    def legs(self):        return 4 class Bird(Animal):    """Concrete bird class."""        @property    def sound(self):        return "Tweet!"        @property    def legs(self):        return 2 # Usagedog = Dog("Buddy")print(dog.make_sound())   # Buddy says Woof!print(dog.legs)           # 4 bird = Bird("Tweety")print(bird.make_sound())  # Tweety says Tweet!print(bird.legs)          # 2

Interface Pattern

from abc import ABC, abstractmethod class Drawable(ABC):    """Interface for drawable objects."""        @abstractmethod    def draw(self):        """Draw the object."""        pass class Movable(ABC):    """Interface for movable objects."""        @abstractmethod    def move(self, x, y):        """Move to position."""        pass class Resizable(ABC):    """Interface for resizable objects."""        @abstractmethod    def resize(self, factor):        """Resize by factor."""        pass class Square(Drawable, Movable, Resizable):    """Square implements multiple interfaces."""        def __init__(self, size, x=0, y=0):        self.size = size        self.x = x        self.y = y        def draw(self):        return f"Drawing square at ({self.x}, {self.y}) with size {self.size}"        def move(self, x, y):        self.x = x        self.y = y        return f"Moved to ({self.x}, {self.y})"        def resize(self, factor):        self.size *= factor        return f"Resized to {self.size}" # Usagesquare = Square(10)print(square.draw())        # Drawing square at (0, 0) with size 10print(square.move(5, 5))    # Moved to (5, 5)print(square.resize(2))     # Resized to 20print(square.draw())        # Drawing square at (5, 5) with size 20 # Type checkingprint(isinstance(square, Drawable))   # Trueprint(isinstance(square, Movable))    # Trueprint(isinstance(square, Resizable))  # True

Abstract Class with Default Implementation

from abc import ABC, abstractmethod class Repository(ABC):    """Abstract repository pattern."""        def __init__(self):        self._cache = {}        @abstractmethod    def find_by_id(self, id):        """Find entity by ID - must implement."""        pass        @abstractmethod    def save(self, entity):        """Save entity - must implement."""        pass        @abstractmethod    def delete(self, id):        """Delete entity - must implement."""        pass        def find_all(self):        """        Default implementation - can be overridden.        Not abstract, so subclasses can use or override.        """        return list(self._cache.values())        def exists(self, id):        """Concrete helper method."""        return id in self._cache class UserRepository(Repository):    """Concrete user repository."""        def find_by_id(self, id):        """Find user by ID."""        return self._cache.get(id)        def save(self, user):        """Save user."""        self._cache[user['id']] = user        return user        def delete(self, id):        """Delete user."""        if id in self._cache:            del self._cache[id]            return True        return False        def find_by_email(self, email):        """Additional method specific to users."""        for user in self._cache.values():            if user.get('email') == email:                return user        return None # Usagerepo = UserRepository() user1 = {'id': 1, 'name': 'Alice', 'email': '[email protected]'}user2 = {'id': 2, 'name': 'Bob', 'email': '[email protected]'} repo.save(user1)repo.save(user2) print(repo.find_by_id(1))              # {'id': 1, 'name': 'Alice', ...}print(repo.find_all())                 # List of all usersprint(repo.exists(1))                  # Trueprint(repo.find_by_email('[email protected]'))  # User dict

Multiple Inheritance

Multiple inheritance cho phép class kế thừa từ nhiều parent classes.

Basic Multiple Inheritance

class Flyable:    """Mixin for flying capability."""        def fly(self):        return f"{self.name} is flying" class Swimmable:    """Mixin for swimming capability."""        def swim(self):        return f"{self.name} is swimming" class Walkable:    """Mixin for walking capability."""        def walk(self):        return f"{self.name} is walking" class Animal:    """Base animal class."""        def __init__(self, name):        self.name = name        def eat(self):        return f"{self.name} is eating" class Duck(Animal, Flyable, Swimmable, Walkable):    """Duck can fly, swim, and walk."""    pass class Fish(Animal, Swimmable):    """Fish can only swim."""    pass class Bird(Animal, Flyable, Walkable):    """Bird can fly and walk."""    pass # Usageduck = Duck("Donald")print(duck.fly())    # Donald is flyingprint(duck.swim())   # Donald is swimmingprint(duck.walk())   # Donald is walkingprint(duck.eat())    # Donald is eating fish = Fish("Nemo")print(fish.swim())   # Nemo is swimming# print(fish.fly())  # AttributeError

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):    """Inherits from both B and C."""    pass # MRO determines which method is calledd = D()print(d.method())    # B (B comes before C in inheritance list) # Check MROprint(D.__mro__)# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>) # Visual MROprint(D.mro())# [D, B, C, A, object]

Diamond Problem Solution

class Base:    """Base class."""        def __init__(self):        print("Base.__init__")        self.value = "base" class Left(Base):    """Left branch."""        def __init__(self):        print("Left.__init__")        super().__init__()  # Calls next in MRO        self.left = "left" class Right(Base):    """Right branch."""        def __init__(self):        print("Right.__init__")        super().__init__()  # Calls next in MRO        self.right = "right" class Bottom(Left, Right):    """Diamond bottom - inherits from both."""        def __init__(self):        print("Bottom.__init__")        super().__init__()  # Calls next in MRO (Left)        self.bottom = "bottom" # Usageobj = Bottom()# Output:# Bottom.__init__# Left.__init__# Right.__init__# Base.__init__ print(Bottom.__mro__)# (<class 'Bottom'>, <class 'Left'>, <class 'Right'>, #  <class 'Base'>, <class 'object'>) # Base.__init__ called only once!print(obj.value)    # baseprint(obj.left)     # leftprint(obj.right)    # rightprint(obj.bottom)   # bottom

Mixins

Mixins là small classes cung cấp specific functionality.

Comparison Mixin

class ComparableMixin:    """Mixin for comparison operations."""        def __eq__(self, other):        """Equal to."""        if not isinstance(other, self.__class__):            return NotImplemented        return self._compare_key() == other._compare_key()        def __lt__(self, other):        """Less than."""        if not isinstance(other, self.__class__):            return NotImplemented        return self._compare_key() < other._compare_key()        def __le__(self, other):        """Less than or equal."""        return self == other or self < other        def __gt__(self, other):        """Greater than."""        return not self <= other        def __ge__(self, other):        """Greater than or equal."""        return not self < other        def __ne__(self, other):        """Not equal."""        return not self == other        def _compare_key(self):        """Subclass must implement this."""        raise NotImplementedError("Subclass must implement _compare_key()") class Person(ComparableMixin):    """Person with age comparison."""        def __init__(self, name, age):        self.name = name        self.age = age        def _compare_key(self):        """Compare by age."""        return self.age        def __repr__(self):        return f"Person('{self.name}', {self.age})" # Usagep1 = Person("Alice", 25)p2 = Person("Bob", 30)p3 = Person("Charlie", 25) print(p1 < p2)      # Trueprint(p1 == p3)     # Trueprint(p2 > p1)      # True # Sorting works!people = [p2, p1, p3]people.sort()print(people)       # [Person('Alice', 25), Person('Charlie', 25), Person('Bob', 30)]

Serialization Mixin

import json class JsonSerializableMixin:    """Mixin for JSON serialization."""        def to_json(self):        """Convert to JSON string."""        return json.dumps(self.to_dict(), indent=2)        def to_dict(self):        """        Convert to dictionary.        Subclass can override to customize.        """        return {            key: value            for key, value in self.__dict__.items()            if not key.startswith('_')        }        @classmethod    def from_json(cls, json_string):        """Create instance from JSON."""        data = json.loads(json_string)        return cls.from_dict(data)        @classmethod    def from_dict(cls, data):        """Create instance from dict - subclass must implement."""        raise NotImplementedError("Subclass must implement from_dict()") class User(JsonSerializableMixin):    """User with JSON serialization."""        def __init__(self, username, email, age):        self.username = username        self.email = email        self.age = age        self._password = None  # Private, won't be serialized        @classmethod    def from_dict(cls, data):        return cls(data['username'], data['email'], data['age'])        def __repr__(self):        return f"User('{self.username}', '{self.email}', {self.age})" # Usageuser = User("alice", "[email protected]", 25) # Serializejson_str = user.to_json()print(json_str)# {#   "username": "alice",#   "email": "[email protected]",#   "age": 25# } # Deserializeuser2 = User.from_json(json_str)print(user2)  # User('alice', '[email protected]', 25)

Timestamp Mixin

from datetime import datetime class TimestampMixin:    """Mixin for created/updated timestamps."""        def __init__(self, *args, **kwargs):        super().__init__(*args, **kwargs)        self.created_at = datetime.now()        self.updated_at = datetime.now()        def touch(self):        """Update the updated_at timestamp."""        self.updated_at = datetime.now()        @property    def age(self):        """Get age in seconds."""        return (datetime.now() - self.created_at).total_seconds() class Document(TimestampMixin):    """Document with timestamps."""        def __init__(self, title, content):        super().__init__()        self.title = title        self.content = content        def update_content(self, new_content):        """Update content and timestamp."""        self.content = new_content        self.touch() # Usagedoc = Document("My Document", "Initial content") print(doc.created_at)    # 2025-10-27 ...print(doc.age)           # 0.xxx seconds import timetime.sleep(2) doc.update_content("Updated content")print(doc.updated_at)    # 2 seconds laterprint(doc.age)           # ~2 seconds

Metaclasses Basics

Metaclasses là classes của classes. type là default metaclass.

Understanding type

# Creating class the normal wayclass Dog:    def bark(self):        return "Woof!" # Creating class with type()Cat = type('Cat', (), {'meow': lambda self: "Meow!"}) # Both work the samedog = Dog()cat = Cat() print(dog.bark())   # Woof!print(cat.meow())   # Meow! # Everything is instance of typeprint(type(Dog))    # <class 'type'>print(type(Cat))    # <class 'type'>print(type(int))    # <class 'type'>print(type(str))    # <class 'type'>

Custom Metaclass

class UpperAttrMeta(type):    """Metaclass that converts attribute names to uppercase."""        def __new__(mcs, name, bases, namespace):        """Create new class."""        # Convert all attribute names to uppercase        uppercase_namespace = {            key.upper() if not key.startswith('__') else key: value            for key, value in namespace.items()        }                return super().__new__(mcs, name, bases, uppercase_namespace) class MyClass(metaclass=UpperAttrMeta):    """Class using custom metaclass."""        name = "original"    value = 42        def method(self):        return "hello" # Usageobj = MyClass() # Attributes are uppercase!print(obj.NAME)      # originalprint(obj.VALUE)     # 42print(obj.METHOD())  # hello # Original names don't exist# print(obj.name)    # AttributeError

Singleton Metaclass

class SingletonMeta(type):    """Metaclass for singleton pattern."""        _instances = {}        def __call__(cls, *args, **kwargs):        """Control instance creation."""        if cls not in cls._instances:            # Create instance only once            instance = super().__call__(*args, **kwargs)            cls._instances[cls] = instance        return cls._instances[cls] class Database(metaclass=SingletonMeta):    """Singleton database connection."""        def __init__(self, host, port):        print(f"Connecting to {host}:{port}...")        self.host = host        self.port = port        self.connected = True # Usagedb1 = Database("localhost", 5432)# Connecting to localhost:5432... db2 = Database("localhost", 5432)# (no connection message - same instance!) print(db1 is db2)        # Trueprint(id(db1) == id(db2))  # True

Registry Metaclass

class RegistryMeta(type):    """Metaclass that registers all subclasses."""        registry = {}        def __new__(mcs, name, bases, namespace):        """Create and register new class."""        cls = super().__new__(mcs, name, bases, namespace)                # Don't register the base class        if bases:            mcs.registry[name] = cls                return cls        @classmethod    def get_registry(mcs):        """Get all registered classes."""        return mcs.registry.copy() class Plugin(metaclass=RegistryMeta):    """Base plugin class."""    pass class EmailPlugin(Plugin):    """Email plugin."""    def send(self):        return "Sending email..." class SMSPlugin(Plugin):    """SMS plugin."""    def send(self):        return "Sending SMS..." class PushPlugin(Plugin):    """Push notification plugin."""    def send(self):        return "Sending push..." # All plugins automatically registered!print("Registered plugins:")for name, cls in RegistryMeta.get_registry().items():    print(f"  - {name}: {cls}") # Output:# Registered plugins:#   - EmailPlugin: <class 'EmailPlugin'>#   - SMSPlugin: <class 'SMSPlugin'>#   - PushPlugin: <class 'PushPlugin'>

Ví Dụ Thực Tế

1. Plugin System

from abc import ABC, abstractmethod class PluginBase(ABC):    """Abstract base for plugins."""        @abstractmethod    def execute(self, *args, **kwargs):        """Execute plugin."""        pass        @property    @abstractmethod    def name(self):        """Plugin name."""        pass        @property    @abstractmethod    def version(self):        """Plugin version."""        pass class PluginManager:    """Manage plugins."""        def __init__(self):        self.plugins = {}        def register(self, plugin_class):        """Register plugin class."""        if not issubclass(plugin_class, PluginBase):            raise TypeError("Plugin must inherit from PluginBase")                plugin = plugin_class()        self.plugins[plugin.name] = plugin        def execute(self, name, *args, **kwargs):        """Execute plugin by name."""        if name not in self.plugins:            raise ValueError(f"Plugin '{name}' not found")        return self.plugins[name].execute(*args, **kwargs)        def list_plugins(self):        """List all plugins."""        return [            {'name': p.name, 'version': p.version}            for p in self.plugins.values()        ] # Concrete pluginsclass ImageProcessor(PluginBase):    """Image processing plugin."""        @property    def name(self):        return "ImageProcessor"        @property    def version(self):        return "1.0.0"        def execute(self, image_path, operation):        return f"Processing {image_path} with {operation}" class DataExporter(PluginBase):    """Data export plugin."""        @property    def name(self):        return "DataExporter"        @property    def version(self):        return "2.1.0"        def execute(self, data, format):        return f"Exporting data to {format} format" # Usagemanager = PluginManager()manager.register(ImageProcessor)manager.register(DataExporter) print(manager.list_plugins())# [{'name': 'ImageProcessor', 'version': '1.0.0'}, #  {'name': 'DataExporter', 'version': '2.1.0'}] result = manager.execute('ImageProcessor', 'photo.jpg', 'resize')print(result)  # Processing photo.jpg with resize

2. Strategy Pattern with ABC

from abc import ABC, abstractmethod class PaymentStrategy(ABC):    """Abstract payment strategy."""        @abstractmethod    def pay(self, amount):        """Process payment."""        pass        @abstractmethod    def refund(self, amount):        """Process refund."""        pass class CreditCardPayment(PaymentStrategy):    """Credit card payment."""        def __init__(self, card_number, cvv):        self.card_number = card_number        self.cvv = cvv        def pay(self, amount):        return f"Paid ${amount} with credit card ending {self.card_number[-4:]}"        def refund(self, amount):        return f"Refunded ${amount} to credit card" class PayPalPayment(PaymentStrategy):    """PayPal payment."""        def __init__(self, email):        self.email = email        def pay(self, amount):        return f"Paid ${amount} via PayPal ({self.email})"        def refund(self, amount):        return f"Refunded ${amount} to PayPal account" class CryptoPayment(PaymentStrategy):    """Cryptocurrency payment."""        def __init__(self, wallet_address):        self.wallet_address = wallet_address        def pay(self, amount):        return f"Paid ${amount} in crypto to {self.wallet_address[:10]}..."        def refund(self, amount):        return f"Refunded ${amount} in crypto" class Order:    """Order with payment strategy."""        def __init__(self, items, payment_strategy):        self.items = items        self.payment_strategy = payment_strategy        self.total = sum(item['price'] for item in items)        def checkout(self):        """Process order with selected payment method."""        return self.payment_strategy.pay(self.total) # Usageitems = [    {'name': 'Laptop', 'price': 1000},    {'name': 'Mouse', 'price': 50}] # Credit card paymentorder1 = Order(items, CreditCardPayment("1234567890123456", "123"))print(order1.checkout())# Paid $1050 with credit card ending 3456 # PayPal paymentorder2 = Order(items, PayPalPayment("[email protected]"))print(order2.checkout())# Paid $1050 via PayPal ([email protected]) # Crypto paymentorder3 = Order(items, CryptoPayment("0x1234567890abcdef"))print(order3.checkout())# Paid $1050 in crypto to 0x12345678...

3. Validation Framework

from abc import ABC, abstractmethod class Validator(ABC):    """Abstract validator."""        @abstractmethod    def validate(self, value):        """Validate value."""        pass class RequiredValidator(Validator):    """Validate required field."""        def validate(self, value):        if value is None or value == "":            raise ValueError("Field is required")        return True class LengthValidator(Validator):    """Validate string length."""        def __init__(self, min_length=None, max_length=None):        self.min_length = min_length        self.max_length = max_length        def validate(self, value):        if self.min_length and len(value) < self.min_length:            raise ValueError(f"Minimum length is {self.min_length}")        if self.max_length and len(value) > self.max_length:            raise ValueError(f"Maximum length is {self.max_length}")        return True class RangeValidator(Validator):    """Validate numeric range."""        def __init__(self, min_value=None, max_value=None):        self.min_value = min_value        self.max_value = max_value        def validate(self, value):        if self.min_value is not None and value < self.min_value:            raise ValueError(f"Minimum value is {self.min_value}")        if self.max_value is not None and value > self.max_value:            raise ValueError(f"Maximum value is {self.max_value}")        return True class ValidatedField:    """Field with validators."""        def __init__(self, *validators):        self.validators = validators        self.storage_name = None        def __set_name__(self, owner, name):        self.storage_name = f'_{name}'        def __get__(self, instance, owner):        if instance is None:            return self        return getattr(instance, self.storage_name, None)        def __set__(self, instance, value):        # Run all validators        for validator in self.validators:            validator.validate(value)        setattr(instance, self.storage_name, value) class User:    """User with validated fields."""        username = ValidatedField(        RequiredValidator(),        LengthValidator(min_length=3, max_length=20)    )        age = ValidatedField(        RequiredValidator(),        RangeValidator(min_value=0, max_value=150)    )        email = ValidatedField(        RequiredValidator(),        LengthValidator(min_length=5, max_length=100)    )        def __init__(self, username, age, email):        self.username = username        self.age = age        self.email = email # Usageuser = User("alice123", 25, "[email protected]")print(user.username)  # alice123 # Validation errors# User("al", 25, "[email protected]")    # ValueError: Minimum length is 3# User("alice123", 200, "[email protected]")  # ValueError: Maximum value is 150# User("alice123", 25, "")               # ValueError: Field is required

Best Practices

# 1. Use ABC for clear interfacesfrom abc import ABC, abstractmethod class Repository(ABC):    @abstractmethod    def save(self, entity):        pass # 2. Keep mixins focused and smallclass LoggingMixin:    def log(self, message):        print(f"[LOG] {message}") # 3. Document MRO implicationsclass MyClass(Base, Mixin1, Mixin2):    """    MRO: MyClass -> Base -> Mixin1 -> Mixin2 -> object    """    pass # 4. Use super() in multiple inheritanceclass Child(Parent1, Parent2):    def __init__(self):        super().__init__()  # Follows MRO correctly # 5. Avoid complex metaclasses# Use metaclasses only when necessary# Decorators/descriptors often simpler

Bài Tập Thực Hành

Bài 1: Shape Hierarchy

Tạo abstract Shape với concrete Triangle, Square, Pentagon.

Bài 2: Storage Backend

Tạo abstract StorageBackend với FileStorage, DatabaseStorage, CloudStorage.

Bài 3: Mixins Suite

Tạo LoggingMixin, CachingMixin, ValidationMixin.

Bài 4: Command Pattern

Implement Command pattern với ABC.

Bài 5: Auto-register Metaclass

Tạo metaclass tự động register classes vào registry.

Tóm Tắt

ABC: Abstract Base Classes với @abstractmethod
Interfaces: Multiple abstract classes
Multiple Inheritance: Kế thừa nhiều classes
MRO: Method Resolution Order (C3 linearization)
Mixins: Small, focused classes cho functionality
Metaclasses: Classes của classes (type)
Patterns: Plugin, Strategy, Registry với ABC

Bài Tiếp Theo

Bài 2: Decorators - Function decorators, decorators với parameters, class decorators, và functools!


Remember:

  • ABC = Clear contracts
  • Multiple inheritance = Use with care
  • Mixins = Small & focused
  • Metaclasses = Power, use wisely
  • super() = Always use in multiple inheritance! 🎯