CodeGym /Các khóa học /C# SELF /Cú pháp kế thừa và base

Cú pháp kế thừa và base trong C#

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Trong bài này tụi mình sẽ cùng tìm hiểu cú pháp kế thừa trong C#: học cách khai báo class dẫn xuất, hiểu từ khóa base là gì và dùng nó để truy cập class cha như thế nào. Làm được mấy cái này thì code của bạn vừa gọn vừa đẹp (đôi khi còn tiết kiệm được thời gian fix bug do copy-paste nữa, tha hồ ngủ nướng!).

Ví dụ thực tế nè: mình có class Vehicle (phương tiện di chuyển) — kiểu như bản thiết kế tổng quát. Tất cả phương tiện đều có thể chạy, có màu sắc, hãng sản xuất, v.v.:

public class Vehicle
{
    public string Brand { get; set; }
    public string Color { get; set; }

    public void Drive()
    {
        Console.WriteLine("Chạy thôi!");
    }
}
Class cơ sở Vehicle

Giờ mình muốn thêm loại phương tiện mới — ô tô. Ô tô vẫn là phương tiện, nhưng có đặc điểm riêng (ví dụ số cửa). Viết lại hết mấy cái màu sắc, hãng sản xuất thì thừa quá, vì đã có rồi mà!

Kế thừa cho phép mình nói: "Ô tô là phương tiện, chỉ là có thêm mấy cái đặc trưng riêng thôi."

2. Khai báo class con như thế nào

Trong C#, để tạo class kế thừa thì dùng dấu hai chấm :. Sau tên class là tên class cha mà bạn muốn kế thừa.


public class Car : Vehicle
{
    public int NumberOfDoors { get; set; }
}
Khai báo class kế thừa Car

Đọc như này nè: class Car kế thừa tất cả thuộc tính và phương thức của class Vehicle và thêm mấy cái đặc trưng riêng — ví dụ số cửa.

Lưu ý: C# chỉ hỗ trợ kế thừa đơn (tức là một class chỉ kế thừa được MỘT class cha thôi). Nhưng interface thì muốn bao nhiêu cũng được nhé! (phần này nói sau).

Nó hoạt động ra sao?

Khi bạn khai báo:

Car myCar = new Car();

...thì object myCar sẽ truy cập được cả thuộc tính/phương thức của Vehicle lẫn thuộc tính riêng của nó. Ví dụ:

myCar.Brand = "Toyota";
myCar.Color = "Xanh dương";
myCar.NumberOfDoors = 4;
myCar.Drive(); // Phương thức kế thừa

Tình huống thực tế:
Trong app "quản lý phương tiện" của mình, bạn có thể làm menu chọn loại phương tiện, sau đó chương trình sẽ hiển thị hết thuộc tính của nó. Thêm loại phương tiện mới thì code thêm cực ít luôn!

3. Kế thừa constructor

Khi tạo object từ class con, đôi khi bạn muốn khởi tạo luôn thuộc tính của class cha qua constructor. Ví dụ, để chắc chắn không quên nhập hãng và màu:


public class Vehicle
{
    public string Brand { get; set; }
    public string Color { get; set; }

    public Vehicle(string brand, string color)
    {
        Brand = brand;
        Color = color;
    }
}

Giờ trong constructor của class con, bạn nên gọi constructor của class cha! Dùng từ khóa base nhé:


public class Car : Vehicle
{
    public int NumberOfDoors { get; set; }

    public Car(string brand, string color, int numberOfDoors)
        : base(brand, color) // gọi constructor Vehicle!
    {
        NumberOfDoors = numberOfDoors;
    }
}

Thực tế nó trông thế nào?

Car bmw = new Car("BMW", "Đen", 4);
Console.WriteLine($"{bmw.Brand}, {bmw.Color}, cửa: {bmw.NumberOfDoors}");
bmw.Drive(); // Vẫn chạy ngon!

Vậy là bmw vừa là ô tô, vừa là phương tiện luôn. Nó nhớ "cha mẹ" của mình mà.

4. Phân biệt base với this

Trong C# có hai từ khóa để làm việc với thành viên class: thisbase.

  • this — truy cập object hiện tại (ví dụ thuộc tính hoặc phương thức của chính nó).
  • basetruy cập thành viên của class cha (tức là "bố mẹ").

Khi nào dùng base? Hãy tưởng tượng bạn muốn mở rộng logic của phương thức trong class cha, hoặc chỉ đơn giản là khởi tạo class cha từ class con. Ví dụ:


public class Bicycle : Vehicle
{
    public int NumberOfGears { get; set; }

    public Bicycle(string brand, string color, int gears)
        : base(brand, color)    // gọi constructor class cha
    {
        NumberOfGears = gears;
    }

    public void ShowInfo()
    {
        // Dùng thuộc tính từ class cha!
        Console.WriteLine($"{Brand} ({Color}), số truyền: {NumberOfGears}");
    }
}

5. Từ khóa base trong phương thức

base không chỉ dùng cho constructor đâu nhé! Đôi khi bạn muốn thay đổi hành vi của phương thức class cha, nhưng vẫn muốn dùng một phần logic của nó.

Giả sử mình muốn phương thức Drive trong class Car in thêm thông tin về xe đang lái:


public class Car : Vehicle
{
    public int NumberOfDoors { get; set; }

    public Car(string brand, string color, int numberOfDoors) 
        : base(brand, color)
    {
        NumberOfDoors = numberOfDoors;
    }

    public void DriveCar()
    {
        base.Drive(); // gọi hàm gốc
        Console.WriteLine($"Lái {Brand} với {NumberOfDoors} cửa!");
    }
}

Giải thích:
Gọi base.Drive(); giống như bảo: "Bố kể phần của bố trước đi, con bổ sung thêm chi tiết sau!". Kết quả khi gọi DriveCar() sẽ in ra:

Chạy thôi!
Lái BMW với 4 cửa!

Về override phương thức (override) tụi mình sẽ nói kỹ hơn ở bài sau :P

6. Sơ đồ kế thừa

Cùng vẽ sơ đồ quan hệ giữa các class nhé. Sơ đồ đơn giản thôi (chứ UML buổi sáng nhìn đau đầu lắm!):

Vehicle (cơ sở)
   /         \
Car      Bicycle
  • Tất cả mũi tên đi TỪ class con ĐẾN class cha.
  • Tất cả class con đều truy cập được thành viên public/protected của class cha.

Phân biệt public, protectedprivate

Nhắc lại chút cho khỏi nhầm nhé.
Trong class con sẽ thấy:

Bộ sửa phạm vi Nhìn thấy trong class con
public
protected
private Không

Lỗi phổ biến:
Cố truy cập trường private từ class con:


public class Vehicle
{
    private string secret = "Không ai biết đâu!";
}

public class Car : Vehicle
{
    public void RevealSecret()
    {
        Console.WriteLine(secret); // Lỗi! Không thấy được.
    }
}
private — chỉ cho class cha, bí mật gia đình không chia sẻ nhé!

So sánh: class cơ sở vs. class con

Vehicle Car (kế thừa)
Brand kế thừa
Color kế thừa
Drive() kế thừa (hoặc mở rộng)
NumberOfDoors không riêng (thuộc tính đặc biệt)

7. Ví dụ

Nếu tiếp tục phát triển app "Quản lý phương tiện" đơn giản, bạn có thể xây như này:


public class Vehicle
{
    public string Brand { get; set; }
    public string Color { get; set; }

    public Vehicle(string brand, string color)
    {
        Brand = brand;
        Color = color;
    }

    public void Drive()
    {
        Console.WriteLine($"{Brand} {Color} chạy rồi!");
    }
}

public class Car : Vehicle
{
    public int NumberOfDoors { get; set; }

    public Car(string brand, string color, int numberOfDoors)
        : base(brand, color)
    {
        NumberOfDoors = numberOfDoors;
    }

    public void ShowCar()
    {
        Console.WriteLine($"Hãng: {Brand}, Màu: {Color}, Cửa: {NumberOfDoors}");
    }
}

public class Bicycle : Vehicle
{
    public int NumberOfGears { get; set; }

    public Bicycle(string brand, string color, int numberOfGears)
        : base(brand, color)
    {
        NumberOfGears = numberOfGears;
    }
}

Trong hàm chính có thể tạo danh sách phương tiện và mô tả từng cái khác nhau:

Car ford = new Car("Ford", "Đỏ", 4);
ford.ShowCar();
ford.Drive();

Bicycle trek = new Bicycle("Trek", "Xanh lá", 21);
trek.Drive();

8. Lỗi, lưu ý và chút hài hước

Lỗi siêu phổ biến — quên gọi constructor class cha trong class con. Nếu Vehicle không có constructor không tham số, mà bạn không gọi base(...), sẽ bị lỗi biên dịch: compiler sẽ la là không khởi tạo được class cha. Kiểu như xây tầng hai mà chưa có tầng một ấy. Không được đâu. Nhớ truyền dữ liệu lên trên nhé.

Lỗi thứ hai: quên rằng thuộc tính/phương thức của class cha khai báo với private thì class con KHÔNG THẤY ĐƯỢC. Nếu muốn "con cháu" dùng được, hãy dùng protected (phần này nói kỹ ở bài sau).

Và quan trọng nhất: kế thừa rất tiện, nhưng đừng xây cây kế thừa rối rắm quá. Thực tế, nếu chuỗi kế thừa của bạn dài hơn hai-ba lớp thì chắc là có gì đó sai sai rồi. Rất dễ bị lẫn lộn ai kế thừa ai, rồi cuối cùng chẳng biết để làm gì luôn.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION