- Tính kế thừa
- Thành viên protected lớp cơ sở
- Lớp bị niêm phong
- Phương thức khởi tạo và hủy
- Chuyển kiểu với lớp
Tính kế thừa trong C#
Kế thừa là một trong 4 khía cạnh của lập trình hướng đối tượng, nó là khả năng cho phép chúng ta định nghĩa ra một lớp mới dựa trên một lớp khác có sẵn, kế thừa giúp cho việc mở rộng code - bảo trì trở nên dễ hơn.
- Lớp cơ sở là lớp mà được lớp khác kế thừa.
- Lớp kế thừa là lớp kế thừa lại các thuộc tính, phương thức từ lớp cơ sở.
Ví dụ, lớp NhanVien
mô tả chung (thuộc tính) về nhân viên trong một công ty
(chức năng, nhiệm vụ, văn bằng ...), kể cả các ứng xử (phương thức) của nhân viên.
Từ đó kế thừa lại xây dựng nên lớp mới cho nhân viên lê tân NhanVienLeTan
,
nhân viên bán hàng NhanVienBanHang
..., nhưng lớp mới đã kế thừa lại các thông tin của lớp
cơ sở và thêm vào những đặc tính riêng của nó.
Ví dụ, định nghĩa lớp Animal như sau
class Animal { public int Legs {get; set;} public float Weigh {get; set;} public void ShowLegs() { Console.WriteLine($"Legs: {Legs}"); } }
Animal có 2 thuộc tính Legs và Weigh, có 1 phương thức ShowLegs. Dùng nó làm lớp cơ sở để xây dựng lớp kế thừa có tên Cat:
class Cat : Animal { public string food; // thuộc tính mới thêm public Cat() { Legs = 4; // Thuộc tính Legs có sẵn - vì nó kế thừa từ Animal food = "Mouse"; } public void Eat() { Console.WriteLine(meal); } }
Khi sử dụng Cat
Cat cat = new Cat(); cat.ShowLegs(); // Phương thức này kế thừa từ lớp cơ sở cat.Eat(); // phương thức của riêng Cat // Legs: 4 // Mouse
Để khai báo lớp kế thừa dùng cú pháp, lớp cơ sở viết sau ký hiệu :
class LỚP_KẾ_THỪA : LỚP_CƠ_SỞ { //... }
C# không hỗ trợ đa kế thừa (mỗi lớp kế thừa chỉ có một lớp cơ sở)
Thành viên được bảo vệ (protected) của lớp cơ sở
Như đã trình bày về Access Modifiers ở Tìm hiểu tính đóng gói,
không phải tất cả các thành viên của lớp cơ sở (cha) có thể truy cập được từ lớp kế thừa (con). Các thành viên,
khi khai báo nó có thể có các Modify như public
private
protected
internal
với ý nghĩa như sau:
public
thành viên có thể truy cập được bởi code bất kỳ đâu.private
phương thức, thuộc tính, trường khai báo với private chỉ có thể truy cập, gọi bởi các dòng code cùng lớp.protected
phương thức, thuộc tính, trường chỉ có thể truy cập, gọi bởi các dòng code cùng lớp hoặc các lớp kế thừa nó.internal
truy cập được bởi code ở cùng assembly (file).protected internal
truy cập được từ code assembly, hoặc lớp kế thừa nó ở assembly khác.private protected
truy cập được code khi cùng assembly trong cùng lớp, hoặc các lớp kế thừa nó.
Trong kỹ thuật lập trình OOP, để đảm bảo tính đóng gói các thành viên (thuộc tính, phương thức) lớp cơ sở
thường khai báo là protected
, có nghĩa là truy cập được bởi lớp kế thừa - nhưng không truy cập được
bên ngoài lớp.
Ví dụ, ở lớp Animal trên, sửa thuộc tính Legs từ public thành protected
protected int Legs {get; set;}
Lớp Cat vẫn đúng, vì nó vẫn truy cập được thuộc tính Legs, thế nhưng khi sử dụng
Cat cat = new Cat(); int l = cat.Legs; // Lỗi - Legs không cho phép truy cập từ code bên ngoài lớp
Lớp niêm phong sealed
Trong kỹ thuật lập trình, bạn có thể đánh dấu một lớp nào đó không bao giờ trở thành lớp cơ sở để phái sinh
ra lớp khác - lớp đó gọi là bị niêm phong. Muốn niêm phong một lớp chỉ việc thêm từ khóa sealed
sealed class A { } class B : A { // Chỗ này lỗi vì kế thừa lớp bị niêm phong }
Dùng kỹ thuật niêm phong lớp (sealed) để đảm bảo không phái sinh các lớp kế thừa một cách thoải mái, mất kiểm soát, nhất là khi số dự án lớn, nhiều người tham gia.
Phương thức hủy và khởi tạo khi kế thừa
Như đã biết, phương thức khởi tạo chạy khi đối tượng được tạo (new
), vấn đề là khi
có sự kế thừa thì lưu ý: hàm khởi tạo của lớp cơ sở chạy trước,
xong đến hàm khởi tạo của lớp kế thừa.
class A { public A() { Console.WriteLine("A Init"); } } class B : A { public B() { Console.WriteLine("B Init"); } }
Khi sử dụng:
new B(); // KẾT QUẢ // A Init // B Ini
Tuy nhiên, khi phương thức khởi tạo lớp cơ sở có tham số, hoặc ấn định một phương thức khởi tạo của lớp cơ sở (nếu lớp cơ sở có quá tải nhiều phương thức khởi tạo), thì hàm tạo của lớp kế thừa phải chỉ định sẽ khởi chạy phương thức khởi tạo (và truyền tham số) nào của lớp cơ sở.
class A { public A(string mgs) { Console.WriteLine("A Init" + mgs); } } class B : A { public B(string abc) : base(abc) { Console.WriteLine("B Init"); } }
Sau phương thức tạo lớp kế thừa thấy có : base(abc)
đây chính là chỉ ra hàm tạo lớp
cơ sở sẽ chạy, đó là hàm có một tham số - và giá trị tham số được truyền vào.
new B("!ABC"); // KẾT QUẢ // A Init!ABC // B Ini
Đối với các phương thức hủy, khi đối tượng hủy nó sẽ thi hành phương thức hủy của lớp kế thừa trước, rồi mới đến phương thức hủy của lớp cơ sở (ngược với khởi tạo).
Chuyển kiểu - tương thích về kiểu khi sử dụng lớp
Trong chuỗi kế thừa ví dụ lớp A
là lớp cơ sở, kế thừa bởi lớp B
, lớp
B
lại là lớp cơ sở kế thừa bởi C
class A { }; class B : A { }; class C : B { }
Vậy có thể chuyển kiểu từ C
-> B
-> A
. Bạn có thể chuyển kiểu một
cách tường minh (viết tên kiểu muốn chuyển trong ()
trước đối tượng), hay ngầm định.
Chiều ngược lại thì không thể
C c = new C(); a = (A)c; // chuyển kiểu tường minh a = c; // ngầm định a = new C(); // ngầm định B b = c; // ngầm định c = new A(); // lỗi - không thể chuyển kiểu thuận cây kế thừa - Lớp cha không chuyển thành con được
Lớp kế thừa luôn có thể cast (chuyển) về lớp cơ sở
Mã nguồn CS013_Inheritance (git), hoặc tải về tại ex013