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! 🎯