Đa hình và trừu tượng: interface và abstract class dùng thế nào

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Bạn cắm đủ loại thiết bị vào ổ điện mà không cần quan tâm bên trong có gì. Ổ cắm chỉ đặt ra một chuẩn chung. Đó chính là tinh thần của đa hình và trừu tượng trong lập trình: làm việc với "giao diện" thay vì chi tiết.

Đa hình nghĩa là gì?

Đa hình (polymorphism) nghĩa là "nhiều hình thái": cùng một lời gọi nhưng hành vi khác nhau tùy đối tượng thực sự nhận lời gọi. Ví dụ, mọi phương thức thanh toán đều có thao tác thanh_toan(so_tien) nhưng cách làm của thẻ, ví điện tử, tiền mặt hoàn toàn khác.

class ThanhToan:
    def thanh_toan(self, so_tien):
        raise NotImplementedError

class TheNganHang(ThanhToan):
    def thanh_toan(self, so_tien):
        return f"Trừ {so_tien} từ thẻ"

class TienMat(ThanhToan):
    def thanh_toan(self, so_tien):
        return f"Nhận {so_tien} tiền mặt"

def thu_tien(phuong_thuc, so_tien):
    return phuong_thuc.thanh_toan(so_tien)

print(thu_tien(TheNganHang(), 100000))
print(thu_tien(TienMat(), 50000))

Hàm thu_tien không cần biết phương thức cụ thể là gì. Khi thêm hình thức mới, bạn viết thêm một lớp mà không sửa thu_tien. Đây chính là lợi ích lớn nhất: dễ mở rộng, ít sửa code cũ.

Trừu tượng hóa

Trừu tượng (abstraction) là chỉ nêu những gì cần biết để sử dụng, giấu phần còn lại. Bạn dùng len(ds) mà không cần biết danh sách lưu độ dài thế nào. Trong thiết kế lớp, ta thể hiện sự trừu tượng bằng hai công cụ.

Abstract class (lớp trừu tượng)

Lớp không thể tạo object trực tiếp, vừa có thể chứa phương thức đã cài đặt sẵn, vừa có phương thức trừu tượng buộc lớp con phải cài:

from abc import ABC, abstractmethod

class Hinh(ABC):
    @abstractmethod
    def dien_tich(self):
        pass

    def mo_ta(self):
        return f"Diện tích: {self.dien_tich()}"

class Vuong(Hinh):
    def __init__(self, a):
        self.a = a
    def dien_tich(self):
        return self.a ** 2

print(Vuong(4).mo_ta())   # Diện tích: 16
# Hinh()  -> TypeError: không tạo được lớp trừu tượng

Interface (giao diện)

Interface chỉ khai báo "hợp đồng": những phương thức cần có, không có phần thân. Java, C#, TypeScript hỗ trợ interface trực tiếp. Một lớp có thể cài đặt nhiều interface, trong khi thường chỉ kế thừa được một lớp cha. Ví dụ TypeScript:

interface CoTheBay {
  bay(): string;
}

class ChimSao implements CoTheBay {
  bay() { return "Vỗ cánh"; }
}

Khi nào dùng cái nào?

Tình huốngChọn
Các lớp con chia sẻ code chung và trạng tháiAbstract class
Chỉ cần quy định hành vi, các lớp không liên quan nhauInterface
Cần một lớp cài nhiều "vai trò"Interface

Python dùng "duck typing": nếu một đối tượng có phương thức cần thiết thì dùng được, không cần khai báo interface; có câu nói vui rằng nếu nó đi như vịt và kêu như vịt thì ta coi nó là vịt.

Lợi ích thực tế

  • Kiểm thử dễ: thay phần phụ thuộc thật bằng bản giả cùng giao diện.
  • Linh hoạt: đổi cách lưu trữ từ file sang cơ sở dữ liệu mà phần còn lại không đổi.
  • Làm việc nhóm: thống nhất giao diện rồi mỗi người cài một phần.

Ví dụ: hệ thống thông báo dễ mở rộng

Hãy kiểm tra sức mạnh của đa hình bằng thiết kế một bộ gửi thông báo qua nhiều kênh:

class KenhThongBao:
    def gui(self, nguoi_nhan, noi_dung):
        raise NotImplementedError

class Email(KenhThongBao):
    def gui(self, nguoi_nhan, noi_dung):
        print(f"[Email] gửi tới {nguoi_nhan}: {noi_dung}")

class Sms(KenhThongBao):
    def gui(self, nguoi_nhan, noi_dung):
        print(f"[SMS] gửi tới {nguoi_nhan}: {noi_dung}")

def thong_bao_tat_ca(kenh_ds, nguoi_nhan, noi_dung):
    for kenh in kenh_ds:
        kenh.gui(nguoi_nhan, noi_dung)

thong_bao_tat_ca([Email(), Sms()], "lan", "Khóa học mới")

Muốn thêm kênh push, bạn chỉ viết thêm một lớp có phương thức gui. Hàm thong_bao_tat_ca không cần sửa. Đây là nguyên tắc "mở để mở rộng, đóng để sửa đổi". Khi kiểm thử, bạn có thể tạo một kênh giả chỉ ghi lại các tin nhắn để kiểm tra, mà không phải gửi thật.

Lỗi thiết kế thường gặp

  • Interface quá to. Nhét quá nhiều phương thức khiến lớp phải cài những thứ không cần. Hãy chia thành các interface nhỏ, mỗi cái một vai trò.
  • Kiểm tra kiểu thủ công bằng chuỗi if loai == "email" rải khắp nơi: dấu hiệu bạn nên dùng đa hình.
  • Lớp con phá vỡ hợp đồng của lớp cha, ví dụ ném lỗi ở phương thức mà lớp cha cam kết chạy được.
  • Trừu tượng hóa quá sớm. Chỉ tạo interface khi bạn đã có ít nhất hai cách cài đặt thực sự.

Kết luận

Đa hình cho phép đối xử với nhiều loại đối tượng theo cùng một cách, còn trừu tượng giúp ta tập trung vào giao diện thay vì chi tiết. Hãy luyện bằng cách thiết kế một hệ thống thông báo có email, SMS, push cùng chung một giao diện, rồi thêm kênh mới mà không đụng đến code cũ.

Khi đã sẵn sàng đi sâu hơn, bạn có thể tham khảo lộ trình học trong các khóa học tại codebem.com.

author_photo
Nguyễn Đình Cường

0 comment