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
-
Hiểu khái niệm Kế thừa (Inheritance) và nguyên tắc DRY (Don't Repeat Yourself).
-
Nắm vững cú pháp kế thừa bằng dấu hai chấm
:. -
Phân biệt được Access Modifier
protectedso vớiprivatevàpublic. -
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àmMain) 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
-
Kế thừa (
:) giúp Lớp con tái sử dụng toàn bộ Code (Fields, Properties, Methods) từ Lớp cha. -
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. -
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. -
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ử.
-
Tạo Lớp cha
Devicecó thuộc tínhBrand(Hãng sản xuất) và phương thứcPowerOn()(In ra màn hình "Thiết bị đang khởi động..."). -
Viết Constructor cho
Devicenhận vàoBrand. -
Tạo Lớp con
Smartphonekế thừa từDevice. Thêm thuộc tính riêngBatteryCapacity(Dung lượng pin). -
Viết Constructor cho
Smartphonenhận vào cảBrandvàBatteryCapacity, nhớ dùngbaseđể chuyềnBrandcho Lớp cha. -
Thêm phương thức
CheckBattery()vàoSmartphoneđể in ra dung lượng pin. -
Ở hàm
Main, khởi tạo một chiếc iPhone, gọi phương thứcPowerOn()(từ cha) vàCheckBattery()(của con).
All rights reserved