Đa hình
Tính đa hình (Polymorphism) là một trong bốn tính chất cơ bản của OOP. Trong lập trình, thuật ngữ này dùng để ám chỉ đến việc một biến, một hàm hoặc một phương thức có thể tồn tại ở nhiều dạng khác nhau. Đa hình là hiện tượng mà các đối tượng thuộc các class khác nhau có thể biểu diễn cùng một thông điệp theo các cách khác nhau.
Ví dụ:
Một người A có thể là một học sinh, đồng thời là thành viên câu lạc bộ, đồng thời là nhân viên bán thời gian quán café.
Một hành động “làm việc” nếu cho học sinh sẽ là học tập, cho kế toán sẽ là “tính toán”, …
Lập trình viên có thể tận dụng Tính đa hình trong OOP vào những trường hợp sau:
- Khi các lớp con cần dùng phương thức của lớp cha để bổ sung cho một phương thức khác.
- Khi một lớp cần có nhiều phương thức cùng tên nhưng khác tham số (parameter).
Tính đa hình trong OOP gồm có 2 loại:
- Runtime Polymorphism (Đa hình thời gian chạy)
- Compile Time Polymorphism (Đa hình thời gian biên dịch)
Theo đó , tính đa hình cho phép một phương thức thực thi những hành vi khác nhau theo hai hướng: sử dụng phương thức ghi đè (method overriding) hoặc phương thức nạp chồng (method overloading).
- Đa hình thời gian chạy là trường hợp một đối tượng bị ràng buộc với chức năng của chúng ngay tại thời gian chương trình đang chạy. Đa hình thời gian chạy sử dụng phương thức ghi đè.
- Đa hình thời gian biên dịch là trường hợp một đối tượng bị ràng buộc với chức năng của chúng ngay tại thời gian chương trình đang biên dịch. Đa hình thời gian biên dịch sử dụng phương thức nạp chồng.
Phương thức ảo, từ khóa Virtual
Một số tài liệu có viết công dụng của Virtual Function như sau:
“Virtual Function là để khai báo một function ở class cha (base class) mà sau đó các class kế thừa (derived class) có thể override function đó”
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.
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 (lớp cha)
- 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 (lớp kế thừa)
Phương thức thuần ảo, lớp ảo
Hàm thuần ảo (pure virtual function) được sử dụng khi:
- Không cần sử dụng hàm này trong lớp cơ sở, chỉ phục vụ cho lớp dẫn xuất
- Lớp dẫn xuất bắt buộc phải định nghĩa lại hàm thuần ảo (Nạp chồng)
Ví dụ: Chúng ta có một lớp cơ sở là Shape. Các lớp dẫn xuất của lớp Shape là Triangle, Square và Circle. Chúng ta muốn tính diện tích của các hình này.
Chúng ta có thể tạo ra một hàm thuần ảo có tên là calculateArea() trong lớp Shape. Các lớp Triangle, Square và Circle phải định nghĩa lại hàm calculateArea() với công thức tính diện tích khác nhau cho mỗi hình.
Lớp ảo
Có một vấn đề khi một lớp cơ sở được kế thừa bởi nhiều lớp dẫn xuất. Ví dụ, xét tình huống các lớp kế thừa theo sơ đồ như sau:
Lớp B và Lớp C kế thừa từ lớp A. Chúng trở thành A (B) và A (C). Lúc này, nếu ta lại cho D kế thừa từ B và C, ta vô tình tạo ra tới 2 con đường kế thừa về 1 điểm đích.
- D kế thừa thông qua B kế thừa A
- D kế thừa thông qua C kế thừa A
Điều này sẽ khiến trình biên dịch mơ hồ và không biết nên đi theo con đường nào và tạo ra lỗi.
Thay vì kế thừa thông thường, ta sẽ định nghĩa B và C là các lớp ảo, việc sử dụng lớp ảo sẽ tiết kiệm không gian và tránh việc trùng lặp kế thừa.
- Nếu kế thừa thông thường, khi tạo class D kế thừa đồng thời B và C, ta thực chất tạo ra 2 bản sao class A
- Nếu cho class B và class C thành virtual, thì khi tạo class C chúng ta sẽ dùng chung 1 bản class A.