Tính Đa Hình Trong Lập Trình Hướng Đối Tượng

Bài số 9 trong 10 bài của series Hướng đối tượng C++

Trong 2 bài trước chúng ta đã cùng tìm hiểu về tính kế thừa và thực hành làm bài tập về nó. Trong bài này, ta sẽ tiếp tục tìm hiểu thêm 1 tính chất cũng quan trọng không kém đó là tính đa hình trong lập trình hướng đối tượng nhé.

Tính đa hình là gì?

Từ đa hình có nghĩa là có nhiều dạng. Nói một cách đơn giản, chúng ta có thể định nghĩa đa hình là khả năng của một thông điệp được hiển thị dưới nhiều dạng.

Mình lấy một ví dụ thực thế nhé:
Một người cùng một lúc có thể có đặc điểm khác nhau. Giống như một người đàn ông đồng thời là một người cha, một người chồng, một nhân viên. Vì vậy, cùng một người sở hữu những hành vi khác nhau trong các tình huống khác nhau. Điều này được gọi là đa hình.

Đa hình được coi là một trong những tính năng quan trọng của Lập trình hướng đối tượng.

Phân loại đa hình

Trong ngôn ngữ C ++, tính đa hình chủ yếu được chia thành hai loại:

  • Compile time Polymorphism.
  • Runtime Polymorphism.

Tính Đa Hình

Compile time Polymorphism

Tính đa hình này được sử dụng bằng cách nạp chồng hàm hoặc nạp chồng toán tử.

Các bạn có thể xem lại về nạp chồng hàm và nạp chồng toán tử: Tại Đây

Nạp chồng hàm

Sau khi biên dịch và chạy chương trình, ta nhận được kết quả:

Trong ví dụ trên, ta chỉ dùng một hàm duy nhất có tên là func nhưng có thể dùng được cho 3 tình huống khác nhau. Đây là một thể hiện của tính đa hình.

Nạp chồng toán tử

Trong ví dụ trên, ta đã nạp chồng lại toán tử cộng.

Định nghĩa của toán tử cộng chỉ dùng cho số nguyên int, nhưng sau khi nạp chồng lại, ta có thể sử dụng chúng cho số phức.

Đây cũng là một thể hiện của tính đa hình.

Runtime Polymorphism

Tính đa hình được thể hiện ở cách nạp chồng toán tử trong kế thừa.

Sau khi biên dịch và chạy chương trình ta có kết quả

Tại sao lại có sự khác biệt ấy? Tại sao cùng là nạp chồng toán tử trong lớp kế thừa nhưng kết quả lại khác nhau?

Trong ví dụ trên mình đã thêm từ khóa virtual vào hàm print() trong lớp cơ sở base.
Từ khóa virtual này dùng để khai báo một hàm là hàm ảo.

Khi khai báo hàm ảo với từ khóa virtual nghĩa là hàm này sẽ được gọi theo loại đối tượng được trỏ (hoặc tham chiếu), chứ không phải theo loại của con trỏ (hoặc tham chiếu). Và điều này dẫn đến kết quả khác nhau:

  • Nếu không khai báo hàm ảo virtual trình biên dịch sẽ gọi hàm tại lớp cở sở base
  • Nếu dùng hàm ảo virtual trình biên dịch sẽ gọi hàm tại lớp dẫn xuất derived
Mục đích của hàm ảo là gì?

Các hàm ảo sẽ cho phép chúng ta tạo một danh sách các con trỏ lớp cơ sở và các phương thức của bất kỳ lớp dẫn xuất nào mà không cần biết loại đối tượng của lớp dẫn xuất.

Mình lấy một ví dụ cụ thể nhé:

Ta sẽ bắt đầu với một phần mềm quản lý nhân viên.

Đầu tiên, ta sẽ xây dựng một lớp Nhanvien sau đó xây dựng các hàm ảo tangluong(), chuyenphong(), …
Từ lớp Nhanvien này ta sẽ cho kế thừa tới các lớp Baove, NhanvienphongA, NhanvienphongB, … Và tất nhiên các lớp này có thể triển khai riêng biệt các hàm ảo có tại lớp cơ sở Nhanvien.

Trình biên dịch sẽ thực hiện Runtime Polymorphism như thế nào?

Trình biên dịch sẽ duy trì:

  • vtable: Đây là một bảng các con trỏ hàm được duy trì cho mỗi lớp
  • vptr: Đây là một con trỏ tới vtable và được duy trì cho mỗi một đối tượng.
    Bạn có thể xem một ví dụ đơn giản: Tại đây

Runtime Polymorphism

Trình biên dịch sẽ thêm code bổ sung tại 2 chỗ là:

Code trong mỗi hàm khởi tạo. Nó sẽ khởi tạo vptr của đối tượng được tạo và đăt vptr trỏ đến vtable của lớp.

Code với lệnh gọi hàm ảo. Tại bất cứ chỗ nào tính đa hình được thực hiện, trình biên dịch sẽ chèn code để tìm vptr trước bằng cách sử dụng con trỏ hoặc tham chiếu lớp cơ sở. Khi vptr được nạp, vptable của lớp dẫn xuất có thể được truy cập. Sử dụng vtable, địa chỉ của hàm ảo tại lớp dẫn xuất sẽ được truy cập và gọi.

Vậy thì đây có phải là một cách thực hiện Runtime polymorphism chuẩn hay không?

Trên thực tế thì C++ không bắt buộc một Runtime polymorphism chạy chính xác như thế này. Nhưng trình biên dịch thường sử dụng các mô hình với biến thể nhỏ, dựa trên mô hình cơ bản bên trên.

Hàm Pure Virtual trong C++

Với Pure Virtual nghĩa là bạn chỉ dùng hàm ảo tại lớp cơ sở để khai báo, chứ không có bất kì câu lệnh nào bên trong hàm đó.

Trong đoạn code trên, mình đã sửa hàm print() thành một Pure Virtual và tất nhiên kết quả vẫn không hề thay đổi.

Bài viết của mình đến đây là hết rồi, mình rất mong nhận được những ý kiến của các bạn để bài viết của mình ngày một tốt hơn. Vì thế đừng ngần ngại comment bất kì thắc mắc, hay đóng góp nào tại phần bình luận ngay phía dưới nhé. Cảm ơn mọi người rất nhiều. Hẹn gặp lại các bạn trong bài viết tiếp theo.


Tài liệu tham khảo

  1. Geeksforgeeks
Các bài viết trong SeriesBài trước: Bài Tập C++ Về Tính Kế ThừaBài sau: Bài tập lập trình Hướng đối tượng tổng hợp

LEAVE A REPLY

Please enter your comment!
Please enter your name here