29 tháng 4, 2020

Tính kế thừa trong C#

Trong đời sống thực tế, kế thừa là việc được thừa hưởng những gì của người đi trước để lại. Ví dụ như bố của bạn để lại cho bạn 1 mảnh đất, vậy là bạn được thừa kế mảnh đất đó, bạn không cần phải kiếm tiền mua đất, không cần phải đi tìm mua đất nữa, giờ chỉ việc xây nhà trên mảnh đất được thừa kế đó.

Tính kế thừa

Trong lập trình, kế thừa cũng được hiểu tương tự, đó là việc một lớp được thừa hưởng các thuộc tính và phương thức từ lớp cha của nó. 
Ví dụ ta có một lớp Animal (động vật) gồm các thuộc tính là chiều cao, cân nặng, tiếng kêusố chân. Vậy khi ta tạo thêm các lớp con là chó, mèo, , lợn, thì các lớp con này không cần phải khai báo lại các thuộc tính của lớp Animal nữa. Mà các lớp con này sẽ thừa hưởng lại tất cả các thuộc tính đó. 

Ứng dụng của kế thừa
- Cho phép xây dựng 1 lớp mới từ lớp đã có.
  •  Lớp mới gọi là lớp con (subclass) hay lớp dẫn xuất (derived class).
  •  Lớp đã có gọi là lớp cha (superclass) hay lớp cơ sở (base class).
- Cho phép chia sẽ các thông tin chung nhằm tái sử dụng và đồng thời giúp lập trình viên dễ dàng nâng cấp và bảo trì.
- Định nghĩa sự tương thích giữa các lớp, nhờ đó ta có thể chuyển kiểu tự động


1. Khai báo và sử dụng kế thừa

Khai báo:
class <tên lớp con> : <tên lớp cha>
{
      //Nội dung
}

Sử dụng:

Ví dụ 1: Một lớp con kế thừa từ một lớp cha
Mình tạo một lớp Cat, có 3 thuộc tính là cân nặng, chiều cao và số chân
public class Cat
{
      public double Weight;
      public double Height;
      public static int Legs = 4;
}

Tiếp tục mình sẽ tạo một lớp BlackCat là lớp con của Cat, vậy lớp BlackCat này không cần phải khai báo 3 biến Weight, HeightLegs nữa. Chỉ cần khai báo thêm biến Color là được.
public class BlackCat : Cat
{
      public static string Color = "Mau den";
      public BlackCat()
      {
          Weight = 2.2;
          Height = 25;
      }      
      public void Info()
      {
          Console.WriteLine("Nang: {0} kg, cao {1} cm, {2} chan, {3}", Weight, Height, Legs, Color);
      }
}

Trong hàm Main mình thử tạo một đối tượng xem sao nhé
static void Main(string[] args)
{
      BlackCat Cat1 = new BlackCat();
      Cat1.Info();
      Console.ReadLine();
}

Kết quả

Ví dụ 2: Nhiều lớp con kế thừa từ một lớp cha
Tiếp theo ví dụ 1, mình tạo thêm một lớp WhiteCat với thuộc tính như sau
public class WhiteCat : Cat
{
       public static string Color = "Mau trang";
       public WhiteCat()
       {
            Weight = 2.4;
            Height = 23;
       }
       public void Info()
       {
            Console.WriteLine("Nang: {0} kg, cao {1} cm, {2} chan, {3}", Weight, Height, Legs, Color);
       }
}

Trong hàm Main mình tạo thêm 1 đối tượng WhiteCat
static void Main(string[] args)
{
       BlackCat Cat1 = new BlackCat();
       Cat1.Info();

       WhiteCat Cat2 = new WhiteCat();
       Cat2.Info();

       Console.ReadLine();
}

Kết quả:

Ví dụ 3: Kế thừa đa cấp
Giả sử mình có một lớp Animal, 1 lớp Cat là lớp con của Animal, 1 lớp WhiteCat là lớp con của lớp Cat
namespace ke_thua
{
    public class Animal
    {
        public double Weight;
        public double Height;
    }
    public class Cat : Animal
    {
        public static int Legs = 4;
    }
    public class WhiteCat : Cat
    {
        public static string Color = "Mau trang";
        public WhiteCat()
        {
            Weight = 2.4;
            Height = 23;
        }
        public void Info()
        {
            Console.WriteLine("Nang: {0} kg, cao {1} cm, {2} chan, {3}", Weight, Height, Legs, Color);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            WhiteCat Cat2 = new WhiteCat();
            Cat2.Info();

            Console.ReadLine();
        }
    }
}

Vậy là lớp WhiteCat đã thừa hưởng lại thuộc tính cân nặng, chiều cao từ lớp ông nội của nó và thừa hưởng thuộc tính 4 chân từ lớp cha của nó.


Lưu ý:
- Trong C# không hỗ trợ đa kế thừa (1 lớp kế thừa từ nhiều lớp) những lại hỗ trợ thực thi nhiều interface
Các thành phần của lớp cha có được kế thừa xuống lớp con hay không là do phạm vi truy cập của thành phần đó là gì.
  • Thành phần có phạm vi là private thì không được kế thừa.
  • Thành phần có phạm vi là protected, public thì được phép kế thừa.
- Hàm khởi tạo và hàm huỷ bỏ không được kế thừa.


2. Khởi tạo và hủy bỏ trong kế thừa

- Khởi tạo mặc định của lớp cha luôn luôn được gọi mỗi khi có 1 đối tượng thuộc lớp con khởi tạo, được gọi trước hàm khởi tạo của lớp con.
- Nếu như lớp cha có hàm khởi tạo có tham số thì đòi hỏi lớp con phải có hàm khởi tạo tương ứng và thực hiện gọi hàm khởi tạo của lớp cha thông qua từ khoá base.
- Khi đối tượng của lớp con bị huỷ thì hàm huỷ bỏ của nó sẽ được gọi trước sau đó mới gọi hàm huỷ bỏ của lớp cha để huỷ những gì lớp con không huỷ được.

Cú pháp để gọi constructor của lớp cha như sau
public <tên lớp>(<danh sách tham số của lớp con>) : base(<danh sách tham số tương ứng với lớp cha>)
{

}

Ví dụ
namespace ke_thua
{
    public class Animal
    {
        public double Weight;
        public double Height;
        public Animal (double Weight, double Height)
        {
            this.Weight = Weight;
            this.Height = Height;
        }
    }
    public class Cat : Animal
    {
        public static int Legs = 4;      
        public Cat(double Weight, double Height) :base(Weight, Height)
        {
        }
        public void Info()
        {
            Console.WriteLine("Nang: {0} kg, cao {1} cm, {2} chan", Weight, Height, Legs);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Cat Cat1 = new Cat(2.2, 23);
            Cat1.Info();

            Console.ReadLine();
        }
    }
}

3. Xử lý khi trùng tên hàm

Giả sử lớp cha Animal có 1 hàm Info, lớp con Cat cũng cần tạo 1 hàm Info, có kiểu trả về là void, không có tham số truyền vào. 
Vậy trong hàm Main sẽ gọi hàm Info nào?
static void Main(string[] args)
{
      BlackCat Cat1 = new BlackCat();
      Cat1.Info();
      Console.ReadLine();
}

Chương trình sẽ gọi hàm Info của lớp Cat tạo. Đồng thời cũng đưa ra 1 cảnh báo khi biên dịch.
Trong C# có hỗ trợ từ khoá new nhằm đánh dấu đây là 1 hàm mới và hàm kế thừa từ lớp cha sẽ bị che đi khiến bên ngoài không thể gọi được.
Bạn khai báo như sau:
public new void Info()
{

}

Hàm Info của lớp cha sẽ bị che giấu đi, mọi đối tượng bên ngoài chỉ gọi được hàm Info của lớp con.
Từ khoá này chỉ làm tường minh khai báo của hàm Info còn về kết quả khi chạy chương trình thì không có gì thay đổi.
Trong trường hợp bạn muốn gọi lại hàm Info của lớp cha thì bạn có thể sử dụng từ khóa base. Với từ khóa này bạn có thể gọi đến các thành phần của lớp cha.
base.Info();

4. Vấn đề cấp phát vùng nhớ cho đối tượng

Bình thường nếu như 1 đối tượng kiểu Animal không thể khởi tạo vùng nhớ có kiểu Cat được. Nhưng nếu 2 lớp này có quan hệ kế thừa thì hoàn toàn được.
Animal cat1 = new Cat();

Các bạn hãy hiểu như sau:
Một đối tượng thuộc lớp cha có thể tham chiếu đến vùng nhớ của đối tượng thuộc lớp con nhưng ngược lại thì không

Có nghĩa là câu lệnh Animal cat1 = new Cat(); sẽ đúng nhưng Cat cat = new Animal (); sẽ báo lỗi.
Cái này hiểu giống như ép kiểu dữ liệu vậy. 

SHARE THIS

Blogger Nguyễn Dương

Có một câu nói mà mình rất thích đó là "Thật sai lầm khi nghĩ rằng một khi rời khỏi trường học, bạn không cần học thêm điều mới nữa". Chính vì thế mà hãy luôn luôn học hỏi, con người chỉ ngừng phát triển khi ngừng học hỏi. Nếu như bạn chưa hiểu nội dung bài viết hoặc đang khúc mắc khi thao tác thì hãy bình luận phía dưới bài viết để mọi người cùng thảo luận nhé!