Thừa kế cho phép ta tạo một lớp mới mở rộng từ một lớp khác, lớp mới được gọi là lớp con, lớp thừa kế hay lớp dẫn xuất (derived class), mình hay gọi là lớp con cho nhanh.
Lớp cha còn gọi là lớp cơ sở.Ưu điểm dễ thấy nhất là ta có thể tạo ra một lớp con mới tận dụng các tính năng có sẵn được cung cấp từ lớp cha, hay còn gọi là khả năng sử dụng lại.
Ấy nhưng đó lại chẳng phải là nguyên nhân người ta cho ra đời tính chất này, bởi nếu bạn đã có sẵn những tính năng đó thì dù có hay không khả năng thừa kế, bạn vẫn có thể tạo ra những lớp mới gọi lại các tính năng đó.Ưu điểm chính của thừa kế là nhờ có nó ta mới có được đa hình (polymorphism), và nhờ đa hình mà ta có trừu tượng (abstract).
Trừu tượng là thứ mà tất cả những nhà thiết kế phần mềm đều muốn sử dụng càng nhiều càng tốt: các driver cho phép phần mềm giao tiếp với các thiết bị khác nhau khi dùng chung một phương thức, các file system driver giúp tương tác với các file trên đĩa mà không cần quan tâm chúng được định dạng với NTFS, HTFS, FAT hay Ext3, ngôn ngữ SQL cho phép làm việc với các cơ sở dữ liệu mà không cần biết chúng được lưu trữ thế nào, HTTP cho phép xem các trang web mà không cần quan tâm máy chủ của nó hoạt động ra sao… Có vô số các ví dụ như vậy. Vì thực sự thừa kế rất dễ hiểu nên tôi sẽ chỉ giải thích một số khái niệm liên quan:
this: trong mỗi lớp sẽ có các phương thức (method – hàm thành viên) và thuộc tính (property – biến thành viên). Trong mỗi method lại có thể có các biến, tham số… Bản thân lớp cha và lớp con cũng có các method và property riêng biệt. Đôi khi chúng có tên trùng nhau, this đại diện cho đối tượng hiện tại – nơi mà phương thức của bạn đang chạy. Hãy xem ví dụ dưới đây:public class Person{ private int age; public void setAge(int age) { this.age = age; }}Bởi trình dịch luôn ưu tiên định-danh-gần-nhất, nên nếu bỏ this đi, ta sẽ có: tham số age = tham số age (vô nghĩa), có this nó sẽ là: thuộc tính age thuộc lớp hiện tại (Person) = tham số age.Để truy cập đến một thành phần trong lớp cha, ta có thể dùng một từ khóa kiểu như parent, base, MyBase… tùy vào ngôn ngữ bạn đang sử dụng.
virtual: từ khóa virtual cho phép một thuộc tính hay phương thức được overriding trong lớp con, trình thực thi sẽ tự động gọi đến phương thức có trong lớp con phù hợp. Xem ví dụ sau:
public class Shape
{
protected double x, y;
public Shape()
{
}
public Shape(double x, double y)
{
this.x = x;
this.y = y;
}
public virtual double Area()
{
return x * y;
}
}
Bây giờ ta có:
public class Circle : Shape
{
public Circle(double r) : base(r, 0)
{
}
public override double Area()
{
return Math.PI * x * x;
}
}
public class Sphere : Shape
{
public Sphere(double r) : base(r, 0)
{
}
public override double Area()
{
return 4 * PI * x * x;
}
}
double r = 3.0, h = 5.0;
Shape c = new Circle(r);
Shape s = new Sphere(r);
Console.WriteLine("Area of Circle = {0:F2}", c.Area());
Console.WriteLine("Area of Sphere = {0:F2}", s.Area());
Chương trình sẽ in ra diện tích của hình tròn (circle) và hình cầu (sphere), dù rằng cả biến c và biến s đều có kiểu Shape, nhưng phương thức Shape.Area sẽ không được gọi mà thay vào đó, các phương thức Area của từng loại hình sẽ được gọi một cách tương ứng.Nếu bây giờ ta bỏ đi từ khóa virtual trong lớp Shape và chạy lại, chương trình sẽ chỉ gọi Shape.Area, bởi nó không quan tâm c và s thực sự có kiểu gì, và in ra đáp án là 15.0.(Lưu ý là nếu bỏ từ khóa virtual, bạn có thể phải bỏ cả từ khóa override để có thể chạy chương trình).
Đa thừa kế: Đa thừa kế là một chủ đề gây tranh cãi giữa các nhà thiết kế, bởi lợi ích nó mang lại cũng như các nhược điểm của nó. Khi tôi bắt đầu tìm hiểu Java, tôi luôn cảm thấy thiếu thiếu, nhưng rõ ràng sau một thời gian làm việc, tôi đã hoàn toàn có thể sống mà không cần đến đa thừa kế, thậm chí giờ đây tôi còn cảm thấy nó quá phức tạp và rối rắm. Khả năng thừa kế từ hai hay nhiều hơn các lớp cha dễ gây tranh chấp giữa các thành phần bên trong, và nhiều khi bạn thậm chí không thể nhớ được có bao nhiêu thành phần bên trong lớp con
.
Trong hình bên dưới, giả sử có một phương thức f ở A đã được override bởi C, vậy khi đó f ở D sẽ là f (cũ) từ B hay f (mới) từ C?
Giả sử bạn hiểu đúng và chính xác, bạn có dám chắc một người khác sẽ hiểu giống như vậy khi bạn chuyển giao code cho họ?Vậy nên, tôi luôn tránh đa thừa kế, và tôi cũng thật lòng khuyên các bạn như vậy, không phải tự nhiên mà .NET và Java lại bỏ đi khái niệm này.
Như tôi đã nói trong phần 1, 4 khái niệm chính trong OOP có liên hệ chặt chẽ với nhau, vậy nên đừng cố hiểu toàn bộ từng cái một, mà thay vào đó bạn đọc toàn bộ, rồi đọc lại những chỗ chưa hiểu. Ngay chính tôi khi giải thích một khái niệm cũng rất khó để tránh không nhắc đến các khái niệm khác. Bạn cũng thử đọc thêm về Factory design pattern, có thể bạn cũng sẽ hiểu thêm một chút đấy.
Câu hỏi cho bạn tuần này: abstract class và interface thì khác gì nhau?