🌱 [Python] OOP Concepts – Tính đa hình Polymorphism trong Python

🌱 [Python] OOP Concepts – Tính đa hình Polymorphism trong Python

   Trong lập trình hướng đối tượng (OOP), khái niệm polymorphism (đa hình) cho phép một interface chung — chẳng hạn một phương thức hoặc toán tử — được sử dụng bởi nhiều kiểu dữ liệu khác nhau và thực hiện các hành vi khác nhau. Trong Python, nhờ vào tính năng kiểu động, "đa hình" trở nên rất linh hoạt và dễ triển khai.

1 – Giới thiệu về Polymorphism

Polymorphism là gì?

   Từ "polymorphism" xuất phát từ tiếng Hy Lạp: poly = nhiều, morph = hình thức. Trong lập trình OOP, nó ám chỉ khả năng một giao diện chung (method, operation, object), được gọi trên nhiều loại đối tượng khác nhau, nhưng mỗi đối tượng sẽ thực hiện theo cách riêng của nó.

Tại sao cần Polymorphism?

   Một số lý do chính:

  • Tăng tính linh hoạt cho code — bạn có thể viết hàm/method chung mà làm việc với nhiều loại đối tượng khác nhau.
  • Giảm sự phụ thuộc chặt vào kiểu cụ thể — giúp tuân theo nguyên tắc mở/đóng (open/closed).
  • Tái sử dụng và bảo trì code dễ hơn khi nhiều class chia sẻ interface chung.

Python Polymorphism

    Ví dụ đơn giản là mỗi con vật có một "ngôn ngữ" riêng để giao tiếp, thay vì mỗi lần tạo ra một object, ta lại cần gọi các method riêng cho việc nói chuyện, thì có thể tạo ra một mothod chung là "speak" cho tất cả!

2 – Các dạng Polymorphism trong Python

🔹 Duck Typing

    Python cho phép gọi method trên một đối tượng mà không cần kiểm tra kiểu rõ ràng, miễn là đối tượng có method đó. Ví dụ: nếu một đối tượng có method fly(), bạn có thể gọi obj.fly() mà không cần biết nó là class nào.

class Duck:
def fly(self):
print("Duck flying")

class Airplane:
def fly(self):
print("Airplane flying")

def make_it_fly(entity):
entity.fly()

duck = Duck()
plane = Airplane()
make_it_fly(duck) # Duck flying
make_it_fly(plane) # Airplane flying

    Ở đây hàm make_it_fly() không quan tâm entity là class nào, chỉ cần có method fly(). Đây là duck‐typing: tập trung vào hành động (behavior) chứ không kiểu (type).

🔹 Method Overriding

    Khi một class con định nghĩa lại (override) phương thức của class cha, gọi tới phương thức chung sẽ kích hoạt phương thức class con tại runtime. Đây là hình thức polymorphism phổ biến.

class Animal:
def speak(self):
print("Some generic sound")

class Dog(Animal):
def speak(self):
print("Bark")

class Cat(Animal):
def speak(self):
print("Meow")

animals = [Animal(), Dog(), Cat()]
for animal in animals:
animal.speak()
# Output:
# Some generic sound
# Bark
# Meow

    speak() được gọi cho mỗi đối tượng dù kiểu khác nhau. Lúc runtime, Python biết dùng method phù hợp class tương ứng của anima`.

Python Polymorphism

🔹 Operator Overloading

    Toán tử như +, * có thể hoạt động khác nhau tuỳ loại dữ liệu (số, chuỗi, list,…). Đây là polymorphism ở mức toán tử.

class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)

def __str__(self):
return f"Vector({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)

    Toán tử + được định nghĩa lại trong class Vector để thực hiện cộng hai vector.

🔹 Function/Method Polymorphism

    Một số built-in function như len() có thể áp dụng cho nhiều kiểu dữ liệu khác nhau: chuỗi, list, dict…

3 – Kỹ thuật nâng cao

Abstract Base Classes (ABC)

    Python cung cấp module `abc` để định nghĩa các class trừu tượng (abstract base class). class ABC cho phép bạn định nghĩa interface mà các class con bắt buộc phải implement, tạo ra một contract rõ ràng.

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount: float) -> bool:
"""Xử lý thanh toán - phải được implement bởi subclass"""
pass

@abstractmethod
def refund(self, transaction_id: str) -> bool:
"""Hoàn tiền - phải được implement bởi subclass"""
pass

class CreditCardProcessor(PaymentProcessor):
def process_payment(self, amount: float) -> bool:
print(f"Processing credit card payment: ${amount}")
return True

def refund(self, transaction_id: str) -> bool:
print(f"Refunding credit card transaction: {transaction_id}")
return True

class PayPalProcessor(PaymentProcessor):
def process_payment(self, amount: float) -> bool:
print(f"Processing PayPal payment: ${amount}")
return True

def refund(self, transaction_id: str) -> bool:
print(f"Refunding PayPal transaction: {transaction_id}")
return True

# Sử dụng polymorphism
def checkout(processor: PaymentProcessor, amount: float):
if processor.process_payment(amount):
print("Payment successful!")

processors = [CreditCardProcessor(), PayPalProcessor()]
for proc in processors:
checkout(proc, 100.0)

    Lợi ích: Đảm bảo các class con implement đầy đủ các method bắt buộc, không thể khởi tạo trực tiếp abstract class, tạo contract rõ ràng cho các developer khác.

Protocol & Structural Subtyping

    Từ Python 3.8+, module typing cung cấp class Protocol - một cách định nghĩa interface dựa trên cấu trúc (structural subtyping) thay vì kế thừa (nominal subtyping). Đây là duck typing nhưng có type checking.

from typing import Protocol

class Readable(Protocol):
def read(self, size: int = -1) -> str:
...

class FileHandler:
def __init__(self, filename: str):
self.filename = filename

def read(self, size: int = -1) -> str:
with open(self.filename, 'r') as f:
return f.read(size)

class StringBuffer:
def __init__(self):
self.buffer = ""

def read(self, size: int = -1) -> str:
if size == -1:
result = self.buffer
self.buffer = ""
return result
result = self.buffer[:size]
self.buffer = self.buffer[size:]
return result

# Hàm generic làm việc với bất kỳ object nào có method read()
def process_readable(source: Readable) -> str:
content = source.read()
return content.upper()

# Sử dụng - không cần kế thừa, chỉ cần có đúng method
file_handler = FileHandler("test.txt")
string_buffer = StringBuffer()
string_buffer.write("Hello from buffer")
print(process_readable(string_buffer)) # HELLO FROM BUFFER

    So sánh: Protocol sử dụng structural subtyping (chỉ cần có đúng method signature), trong khi ABC sử dụng nominal subtyping (phải kế thừa từ base class). Protocol linh hoạt hơn và phù hợp với duck typing của Python.

Plugin System Architecture

   Một ứng dụng thực tế của polymorphism là xây dựng hệ thống plugin, cho phép mở rộng chức năng mà không sửa code gốc.

from abc import ABC, abstractmethod
from typing import List, Dict, Any

class Plugin(ABC):
@abstractmethod
def get_name(self) -> str:
pass

@abstractmethod
def execute(self, data: Dict[str, Any]) -> Dict[str, Any]:
pass

@abstractmethod
def get_priority(self) -> int:
pass

class ImageCompressorPlugin(Plugin):
def get_name(self) -> str:
return "Image Compressor"

def execute(self, data: Dict[str, Any]) -> Dict[str, Any]:
print(f"[{self.get_name()}] Compressing image...")
data['compressed'] = True
data['size'] = data.get('size', 1000) * 0.5
return data

def get_priority(self) -> int:
return 1

class PluginManager:
def __init__(self):
self.plugins: List[Plugin] = []

def register(self, plugin: Plugin) -> None:
self.plugins.append(plugin)
self.plugins.sort(key=lambda p: p.get_priority())
print(f"Registered plugin: {plugin.get_name()}")

def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
result = data.copy()
for plugin in self.plugins:
result = plugin.execute(result)
return result

# Sử dụng
manager = PluginManager()
manager.register(ImageCompressorPlugin())
result = manager.process({'filename': 'photo.jpg', 'size': 2000})

    Ưu điểm: Mở rộng chức năng mà không sửa code gốc (Open/Closed Principle), dễ dàng thêm/xóa plugins, mỗi plugin độc lập và dễ test.

4 – Lưu ý quan trọng

Best Practices

  • Đảm bảo interface chung rõ ràng — các class có thể triển khai method cùng tên để sử dụng polymorphism.
  • Dùng duck-typing hợp lý: tránh kiểm tra kiểu (type checking) quá nhiều, tận dụng khả năng linh hoạt của Python.
  • Khi overload toán tử, đảm bảo hành vi rõ ràng và nhất quán với người dùng class.
  • Tránh dùng quá nhiều method overloading (Python không hỗ trợ gốc) mà thay vào đó dùng tham số mặc định hoặc *args, **kwargs.
  • Polymorphism giúp viết code mở rộng dễ dàng — thiết kế các class hoặc function sao cho dễ bổ sung loại mới mà không sửa code hiện có.

Các lỗi thường gặp

❌ Lỗi 1: Quên implement method bắt buộc

from abc import ABC, abstractmethod

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

# ❌ SAI: Quên implement area()
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
# TypeError: Can't instantiate abstract class Circle

# ✅ ĐÚNG: Implement đầy đủ
class Circle(Shape):
def __init__(self, radius):
self.radius = radius

def area(self):
return 3.14 * self.radius ** 2

❌ Lỗi 2: Kiểm tra kiểu quá nhiều (Anti-pattern)

# ❌ SAI: Kiểm tra kiểu thủ công - vi phạm polymorphism
def process_animal(animal):
if isinstance(animal, Dog):
animal.bark()
elif isinstance(animal, Cat):
animal.meow()
# Phải thêm elif mỗi khi có loại mới!

# ✅ ĐÚNG: Dùng polymorphism
class Animal:
def make_sound(self):
pass

class Dog(Animal):
def make_sound(self):
print("Bark!")

def process_animal(animal: Animal):
animal.make_sound() # Không cần kiểm tra kiểu!

❌ Lỗi 3: Overload toán tử không nhất quán

# ❌ SAI: __add__ trả về kiểu khác
class Counter:
def __init__(self, value):
self.value = value

def __add__(self, other):
return self.value + other.value # Trả về int!

# ✅ ĐÚNG: Trả về cùng kiểu
class Counter:
def __init__(self, value):
self.value = value

def __add__(self, other):
return Counter(self.value + other.value)

Performance Considerations

   Polymorphism có overhead nhỏ do dynamic dispatch (tìm method tại runtime), nhưng thường không đáng kể.

import time

class Animal:
def speak(self):
return "sound"

class Dog(Animal):
def speak(self):
return "bark"

# Benchmark
def test_direct():
dog = Dog()
start = time.perf_counter()
for _ in range(1_000_000):
result = dog.speak()
end = time.perf_counter()
print(f"Direct call: {end - start:.4f}s")

def test_polymorphic():
animals = [Dog() for _ in range(10)]
start = time.perf_counter()
for _ in range(100_000):
for animal in animals:
result = animal.speak()
end = time.perf_counter()
print(f"Polymorphic call: {end - start:.4f}s")

# Output: Overhead ~4-5%

    Kết luận: Overhead của polymorphism rất nhỏ (< 5%), lợi ích về maintainability lớn hơn nhiều so với cost. Chỉ optimize khi profiling cho thấy đây là bottleneck thực sự.

5 – Kết luận

    Polymorphism là một trong những gốc rễ của OOP — cho phép "một cái tên" (method, operator, function) có thể hành xử theo nhiều cách khác nhau tuỳ ngữ cảnh. Trong Python, với kiểu động và duck typing, bạn có thể khai thác polymorphism một cách rất linh hoạt để viết code tổng quát, mở rộng và dễ bảo trì.

>>>>>> Follow ngay <<<<<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊

Nguyễn Văn Nghĩa

Mình là một người thích học hỏi và chia sẻ các kiến thức về Nhúng IOT.

Đăng nhận xét

Mới hơn Cũ hơn
//