Kế thừa trong lập trình hướng đối tượng là một trong những chủ đề rất quan trọng. Kế thừa trong OOP cho phép chúng ta định nghĩa một class mới dựa trên thuộc tính và phương thức của một class đã có trước. Giúp chúng ta tối ưu được thời gian công sức phát triển ứng dụng, đồng thời cũng dễ dàng nâng cấp và chỉnh sửa mã nguồn. Điều đó có nghĩa là, khi chúng ta định nghĩa một class mới, không nhất thiết phải viết lại tất cả mã nguồn cho nó, mà có thể kế thừa dựa trên 1 class đã tồn tại.
1. Quan hệ giữa các lớp đối tượng
Giữa các lớp đối tượng có những loại quan hệ sau:
a. Quan hệ một một (1-1)
Hai lớp đối tượng được gọi là có quan hệ 1-1 với nhau khi một đối tượng của lớp này quan hệ với một đối tượng của lớp kia và một đối tượng lớp kia cũng quan hệ với đối tượng của lớp này.
Ví dụ:
- Lớp học có 1 giáo viên chủ nhiệm
- 1 vợ có quan hệ hôn nhân với 1 chồng
- 1 quốc gia có 1 thủ đô
b. Quan hệ một nhiều (1-n)
Hai lớp được gọi là có quan hệ một – nhiều với nhau khi mà một đối tượng thuộc lớp này có quan hệ với nhiều đối tượng của lớp kia và một đối tượng của lớp kia có quan hệ duy nhất với 1 đối tượng thuộc lớp này.
Ví dụ:
- Một lớp học có nhiều học sinh
- Một công ty có nhiều nhân viên
- Một nhạc sĩ có nhiều tác phẩm
c. Quan hệ nhiều nhiều (n-n)
Hai lớp được gọi là có quan hệ nhiều nhiều với nhau khi một đối tượng thuộc lớp này có quan hiệu với nhiều đối tượng của lớp kia, và một đối tượng của lớp kia có quan hệ với nhiều đối tượng của lớp này.
Ví dụ:
- Một bác sĩ khám nhiều bệnh nhân và một bênh nhân có nhiều bác sĩ
- Một nam có thể yêu nhiều nữ, và một nữ có thể yêu nhiều nam
d. Quan hệ đặc biệt hóa, tổng quát hóa
Hai lớp đối tượng mà đối tượng này là trường hợp đặc biệt của đối tượng kia và đối tượng kia là trường hợp tổng quát của đối tượng này thì được gọi là quan hệ đặc biệt hóa, tổng quát hóa
Ví dụ:
- Tam giác cân là tam giác
- Sinh viên là con người
2 Kế thừa trong lập trình hướng đối tượng
Kế thừa trong lập trình hướng đối tượng là một đặc điểm dùng để biểu diễn mối quan hệ đặc biệt hóa – tổng quát hóa giữa các class. Các lớp được trừu tượng hóa và tổ chức thành một sơ đồ phân cấp lớp.
Kế thừa thường được sử dụng để biểu diễn quan hệ “là một”, ví dụ như:
- Một sinh viên là một con người
- Một tam giác là một đa giác
a. Lợi ích của kế thừa
– Kế thừa cho phép chúng ta xây dựng lớp mới từ một lớp đã có, có thể sử dụng lại các thuộc tính, phương thức, hay dựa trên đặc điểm của mối quan hệ đặc biệt hóa, tổng quát hóa.
– Tổ chức các lớp dựa trên chia sẻ mã chương trình chung, dễ dàng chỉnh sửa, nâng cấp hệ thống khi cần
b. Đặc tính của kế thừa
– Cho phép định nghĩa lớp mới từ lớp đã có, khi đó:
- Lớp mới được gọi là lớp con (subclass) hay lớp dẫn xuất (derived class).
- Lớp ban đầu gọi là lớp cha (superclass) hay lớp cơ sở (base class).
– Nhiều lớp có thể dẫn xuất từ một lớp cha.
– Một lớp có thể là dẫn xuất của nhiều lớp cha.
3. Khai báo kế thừa đơn
a. Cú pháp khai báo kế thừa
class SuperClass { //Thành phần của lớp cơ sở }; class DerivedClass : public/protected/private SusperClass { //Thành phần bổ sung của lớp dẫn xuất };
b. Truy cập thành viên của lớp
Trong hình trên giới thiệu cho bạn khả năng truy cập thành viên của lớp. Cụ thể ở trên lớp dẫn xuất (derived class) chỉ có thể truy cập các thành phần Public và Protected của lớp cơ sở (base class). Còn các hàm bạn, lớp bạn thì như chúng ta đã biết là có thể truy cập tất cả. Trường hợp Others bên trên, ví dụ như những hàm hoặc lớp không có mối quan hệ với class đó thì chỉ sử dụng được các thành viên public.
c. Xét ví dụ về kế thừa đơn
Giả sử chúng ta có mối quan hệ như sau:”Một sinh viên là một người”
Như vậy chúng ta thử tổ chức kế thừa cho mối quan hệ này, và tất nhiên là Sinh viên kế thừa từ lớp người.
Giả sử chúng ta có một class người gồm thuộc tính họ tên và năm sinh , có phương thức là Giới thiệu bản thân, như sau:
class Nguoi { protected: string hoten; int NamSinh; public: Nguoi() { } ~Nguoi() { } void Gioithieu() { cout << "Toi ten : "<< hoten<<endl; cout << "Nam sinh : "<< NamSinh<<endl; } };
Bây giờ chúng ta xây dựng lớp kế thừa sinh viên từ lớp người, vì bản chất Sinh viên cũng là Người và có các thuộc tính đó, như vậy chúng ta chỉ cần bổ sung thêm thuộc tính và hành động mà chúng ta cần quan tâm đối với một sinh viên, chẳng hạn như: Mã SV, Chuyên ngành,… và một số cần thiết mà chúng ta quan tâm ở Sinh viên như đi học, đóng học phí ,…
class SinhVien : public Nguoi { protected: string chuyennganh; int mssv; public: Nguoi() { } ~Nguoi() { } void Gioithieu() { Nguoi::Gioithieu(); cout << "Ma SV: "<< mssv << endl; cout << "Chuyen nganh: "<< chuyennganh << endl; } void dihoc() { cout << hoten <<" di hoc!\n"; } void donghocphi() { cout << Nguoi::hoten << " dong hoc phi!"<<endl; } };
Những điều cần lưu ý trong code ví dụ bên trên:
– Để gọi và sử dụng hàm của class cha, bạn có thể gọi trực tiếp của nó, tuy nhiên trong trường hợp trùng tên ở class con và class cha thì bạn phải sử dụng phạm vi truy xuất. Nếu không, nó sẽ sử dụng biến/hàm ở class bạn đang viết. Cụ thể trong ví dụ trên mình có thể dùng hoten hoặc Nguoi::hoten để gọi biến của lớp cha.
– class SinhVien : public Nguoi Đây là cú pháp khai báo, ở từ khóa public đó, mình có thể thay bằng private, protected, phần này mình sẽ nói rõ ở phần phạm vi truy xuất trong kế thừa bên dưới.
– Khi bạn kế thừa từ class Người, điều đó có nghĩa là bạn đã tạo ra 1 class mới có các thuộc tính và phương thức của class người và những phần bạn vừa viết thêm cho class con.
4. Phạm vi truy xuất trong kế thừa
Khi cài đặt kế thừa trong lập trình hướng đối tượng người ta vẫn phải quan tâm đến tính đóng gói và che giấu thông tin. Điều này ảnh hưởng đến phạm vi truy xuất các thành phần trong class.
– Truy xuất theo chiều dọc: Hàm thành phần của lớp con có quyền truy xuất các thành phần của lớp cha hay không?
– Truy xuất theo chiều ngang: Các thành phần của lớp cha, sau khi kế thừa xuống lớp con, thì thế giới bên ngoài có quyền truy xuất thông qua đối tượng của lớp con hay không?
a. Truy xuất theo chiều dọc
Về truy xuất theo chiều dọc, dựa theo Phần 3b bên trên. Điều này là do lớp cha quyết định.
b. Truy xuất theo chiều ngang
Còn Truy xuất theo chiều ngang, theo ví dụ bên trên có dòng khai báo class SinhVien : public Nguoi ở đây mình cần xác định nên dùng public, private hay protected. Có thể nói mặc dù các thành phần trong lớp cha khai báo protected hoặc public, nhưng điều đó không có nghĩa là lớp con có thể truy cập các thành phần protected và public của class cha, cũng như với phạm vi ngoài class con khi sử dụng public.
Điều đó có nghĩa là ở truy xuất theo chiều ngang, việc lớp con quyết định kế thừa theo public, protected hay private có ảnh hưởng đến việc truy cập các thành phần của class cha.
Để cho dễ hiểu nhất bạn có thể theo dõi hình bên dưới:
Có thể hiểu rằng, khi kế thừa theo kiểu public, private hay protected thì các thành phần trong class cha cũng thay đổi thuộc thuộc tính theo quy ước, như bên trên:
– class B kế thừa từ A theo kiểu public: thì chỉ có 2 thuộc tính public và protected của lớp A sẽ trở thành public và protected của lớp B. Còn private bên A thì B sẽ không dùng được.
– class C kế thừa từ A theo kiểu protected: 2 thuộc tính public và protected của lớp A sẽ trở thành protected của lớp C.
– class D kế thừa từ A theo kiểu private: 2 thuộc tính public và protected của lớp A sẽ trở thành private của lớp D. Như vậy ở bên ngoài class sẽ không thể truy xuất dữ liệu của class D này.
Thông thường người ta sử dụng kế thừa theo kiểu public.
5. Đa kế thừa
Phần nội dung về đa kế thừa là cơ bản nó cho phép một lớp có thể là dẫn xuất của nhiều lớp cơ sở. Tuy nhiên nó thường ít dùng trong các bài tập của môn học, nên mình sẽ chia sẻ sau về phần này. Các bạn có thể tìm hiểu thêm ở các nguồn khác.
<3 Bài viết rất hay ạ <3
#OOP
rất dễ hiểu !!! thanks ạ!