0

Bài 12: Tính Kế thừa (Inheritance) - Tái sử dụng code hiệu quả

🎯 Mục tiêu bài học

  1. Hiểu khái niệm Kế thừa (Inheritance) và nguyên tắc DRY (Don't Repeat Yourself).

  2. Nắm vững cú pháp kế thừa bằng dấu hai chấm :.

  3. Phân biệt được Access Modifier protected so với private và public.

  4. Sử dụng từ khóa base để gọi hàm khởi tạo (Constructor) và phương thức của Lớp cha.

📖 Phần 1: Tại sao phải dùng Kế thừa?

Hãy tưởng tượng bạn đang viết phần mềm quản lý nhân sự. Bạn có Class Developer (có Tên, Tuổi, Lương, Ngôn ngữ lập trình) và Class Manager (có Tên, Tuổi, Lương, Số nhân viên quản lý). Nếu viết bình thường, bạn sẽ phải lặp lại việc khai báo Tên, Tuổi, Lương ở cả 2 Class. Nếu sau này cần thêm thuộc tính "Số điện thoại", bạn lại phải đi sửa ở từng Class một.

Tính Kế thừa cho phép bạn tạo ra một Lớp cha (Base Class / Parent Class) chứa tất cả các đặc điểm chung (Tên, Tuổi, Lương). Sau đó, các Lớp con (Derived Class / Child Class) sẽ "kế thừa" lại toàn bộ đặc điểm đó mà không cần viết lại mã nguồn, chỉ việc khai báo thêm những gì đặc thù của riêng nó.

💡 Quy tắc vàng: Trong C#, một Lớp con chỉ được phép kế thừa từ một Lớp cha duy nhất (Single Inheritance).

📖 Phần 2: Cú pháp Kế thừa và từ khóa protected

Để một Class kế thừa từ một Class khác, ta sử dụng dấu hai chấm :.

Khi dùng kế thừa, bạn sẽ làm quen với một mức độ bảo mật mới: protected.

  • private: Bị khóa kín, chỉ nội bộ Class đó mới truy cập được (Lớp con cũng không được đụng tới).

  • public: Mở toang, ai cũng truy cập được.

  • protected: "Của gia đình" - Chỉ bản thân Class đó và các Lớp con kế thừa từ nó mới được phép truy cập. Bên ngoài (hàm Main) vẫn bị cấm.

C#

// Lớp cha (Base Class)
class Employee 
{
    public string FullName { get; set; }
    protected double Salary { get; set; } // Lớp con được dùng, hàm Main không được dùng

    public void CheckIn()
    {
        Console.WriteLine($"{FullName} da cham cong.");
    }
}

// Lớp con (Derived Class) kế thừa từ Employee
class Developer : Employee
{
    public string ProgrammingLanguage { get; set; }

    public void WriteCode()
    {
        // Sử dụng được cả thuộc tính của riêng nó và thuộc tính Salary của Lớp cha
        Console.WriteLine($"{FullName} dang code bang {ProgrammingLanguage}.");
        Console.WriteLine($"Muc luong (bao mat): {Salary}"); // Hợp lệ vì Salary là protected
    }
}

📖 Phần 3: Từ khóa base - Kế thừa Constructor

Khi bạn tạo một Đối tượng của Lớp con, C# bắt buộc phải khởi tạo Lớp cha trước (giống như phải có "Cha" rồi mới sinh ra "Con"). Nếu Lớp cha có Constructor yêu cầu tham số, Lớp con phải thu thập tham số đó và "chuyền" lên cho Lớp cha thông qua từ khóa base.

C#

class User
{
    public string Username { get; set; }

    // Constructor của Lớp cha
    public User(string username)
    {
        Username = username;
        Console.WriteLine("=> [Log] Tao User he thong...");
    }
}

class Admin : User
{
    public string RoleLevel { get; set; }

    // Constructor của Lớp con chuyền dữ liệu lên Lớp cha bằng : base()
    public Admin(string username, string level) : base(username)
    {
        RoleLevel = level;
        Console.WriteLine("=> [Log] Cap quyen Admin...");
    }
}

💻 Phần 4: Thực hành

Chúng ta sẽ mô phỏng một hệ thống quản lý giao dịch thanh toán cơ bản. Payment là Lớp cha, CreditCardPayment và MomoPayment là các Lớp con:

C#

using System;

namespace Lesson12
{
    // Lớp cha chứa logic chung
    class Payment
    {
        public string TransactionId { get; set; }
        protected double Amount { get; set; }

        public Payment(string id, double amount)
        {
            TransactionId = id;
            Amount = amount;
        }

        public void ProcessBasePayment()
        {
            Console.WriteLine($"[{TransactionId}] Dang xu ly giao dich tri gia: {Amount:N0} VND...");
        }
    }

    // Lớp con kế thừa thanh toán bằng thẻ
    class CreditCardPayment : Payment
    {
        public string CardNumber { get; set; }

        public CreditCardPayment(string id, double amount, string cardNum) : base(id, amount)
        {
            CardNumber = cardNum;
        }

        public void ProcessWithCard()
        {
            ProcessBasePayment(); // Gọi hàm của Lớp cha
            Console.WriteLine($"=> Xac thuc the tin dung so {CardNumber} - Thanh cong!\n");
        }
    }

    // Lớp con kế thừa thanh toán bằng Momo
    class MomoPayment : Payment
    {
        public string PhoneNumber { get; set; }

        public MomoPayment(string id, double amount, string phone) : base(id, amount)
        {
            PhoneNumber = phone;
        }

        public void ProcessWithMomo()
        {
            ProcessBasePayment(); 
            Console.WriteLine($"=> Gui OTP den so {PhoneNumber} - Thanh cong!\n");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            CreditCardPayment p1 = new CreditCardPayment("TXN001", 500000, "4123-XXXX-XXXX");
            p1.ProcessWithCard();

            MomoPayment p2 = new MomoPayment("TXN002", 150000, "0909123456");
            p2.ProcessWithMomo();

            Console.ReadLine();
        }
    }
}

📝 Tóm tắt bài học

  1. Kế thừa (:) giúp Lớp con tái sử dụng toàn bộ Code (Fields, Properties, Methods) từ Lớp cha.

  2. Dùng protected để che giấu dữ liệu với bên ngoài, nhưng vẫn cho phép các Lớp con trực tiếp sử dụng.

  3. Dùng base(...) ở Constructor Lớp con để gọi Constructor Lớp cha và truyền các tham số cần thiết lên trên.

  4. Một Class chỉ có thể kế thừa trực tiếp từ 1 Class duy nhất.

🛠 Bài tập thực hành

Yêu cầu: Mô phỏng thiết bị điện tử.

  1. Tạo Lớp cha Device có thuộc tính Brand (Hãng sản xuất) và phương thức PowerOn() (In ra màn hình "Thiết bị đang khởi động...").

  2. Viết Constructor cho Device nhận vào Brand.

  3. Tạo Lớp con Smartphone kế thừa từ Device. Thêm thuộc tính riêng BatteryCapacity (Dung lượng pin).

  4. Viết Constructor cho Smartphone nhận vào cả Brand và BatteryCapacity, nhớ dùng base để chuyền Brand cho Lớp cha.

  5. Thêm phương thức CheckBattery() vào Smartphone để in ra dung lượng pin.

  6. Ở hàm Main, khởi tạo một chiếc iPhone, gọi phương thức PowerOn() (từ cha) và CheckBattery() (của con).


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí