Bài 13: Tính Đa hình (Polymorphism) & Overriding
🎯 Mục tiêu bài học
-
Hiểu khái niệm Đa hình (Polymorphism) - Trụ cột thứ 3 của OOP.
-
Nắm vững cách sử dụng cặp từ khóa
virtual(cho phép ghi đè) vàoverride(thực hiện ghi đè). -
Thấy được sức mạnh của Upcasting: Quản lý danh sách đối tượng con thông qua kiểu dữ liệu của Lớp cha.
📖 Phần 1: Đa hình là gì?
Đa hình (Polymorphism) xuất phát từ tiếng Hy Lạp có nghĩa là "Nhiều hình thái". Trong lập trình, nó có nghĩa là: Cùng một hành động (phương thức), nhưng các đối tượng khác nhau sẽ có cách thực hiện khác nhau.
Ví dụ ngoài đời: Cùng là hành động "Gửi thông báo", nhưng Email thì gửi qua mạng Internet, SMS thì gửi qua sóng viễn thông, còn Push Notification thì gửi qua App.
Nếu không có Đa hình, bạn sẽ phải viết hàng loạt câu lệnh if-else để kiểm tra xem đang gửi loại thông báo nào, code sẽ rất cứng nhắc và khó mở rộng. Đa hình giúp bạn giải quyết việc này một cách cực kỳ thanh lịch.
📖 Phần 2: Cặp bài trùng virtual và override
Để một Lớp con có thể "thay đổi" hoặc "viết lại" một hành vi (Method) đã được định nghĩa ở Lớp cha, C# cung cấp một cơ chế gọi là Ghi đè (Overriding).
-
Ở Lớp cha: Đặt từ khóa
virtualvào trước phương thức. Nó mang ý nghĩa: "Tôi định nghĩa hành động này ở đây, nhưng nếu Lớp con nào muốn làm khác đi thì cứ tự nhiên ghi đè lại nhé". -
Ở Lớp con: Đặt từ khóa
overridevào trước phương thức cùng tên. Nó mang ý nghĩa: "Tôi sẽ bỏ qua cách làm của cha, và thực hiện theo cách riêng của tôi".
Cú pháp cơ bản:
C#
class Animal
{
// Từ khóa virtual cho phép lớp con ghi đè
public virtual void MakeSound()
{
Console.WriteLine("Động vật đang kêu...");
}
}
class Dog : Animal
{
// Từ khóa override thực hiện ghi đè
public override void MakeSound()
{
Console.WriteLine("Gâu gâu!");
}
}
class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("Meo meo!");
}
}
📖 Phần 3: Sức mạnh thực sự - Upcasting
Điều tuyệt vời nhất của Tính đa hình không chỉ nằm ở việc ghi đè, mà nằm ở khả năng Upcasting (Ép kiểu lên cha). Bạn có thể dùng một biến mang kiểu dữ liệu của Lớp cha để chứa đối tượng của Lớp con.
Kết hợp với danh sách (List), bạn có thể duyệt qua hàng loạt đối tượng con khác nhau và gọi cùng một phương thức, mỗi đối tượng sẽ tự biết cách phản hồi đúng theo bản chất của nó!
C#
// Quản lý tất cả chó và mèo chung trong một List kiểu Animal
List<Animal> pets = new List<Animal>();
pets.Add(new Dog());
pets.Add(new Cat());
pets.Add(new Dog());
// Gọi MakeSound(), mỗi con sẽ tự kêu theo cách riêng
foreach (Animal pet in pets)
{
pet.MakeSound();
}
/*
Kết quả:
Gâu gâu!
Meo meo!
Gâu gâu!
*/
💻 Phần 4: Thực hành (Mô phỏng hệ thống thông báo Backend)
Chúng ta sẽ viết một hệ thống tính lương thưởng cho nhân viên. Các vị trí khác nhau (Developer, Manager) sẽ có công thức tính thưởng cuối năm (Bonus) hoàn toàn khác nhau.
C#
using System;
using System.Collections.Generic;
namespace Lesson13
{
// 1. Lớp cha
class Employee
{
public string Name { get; set; }
public double BaseSalary { get; set; }
public Employee(string name, double salary)
{
Name = name;
BaseSalary = salary;
}
// Cho phép ghi đè phương thức tính thưởng
public virtual double CalculateBonus()
{
return BaseSalary * 0.1; // Mặc định thưởng 10% lương cơ bản
}
}
// 2. Lớp con: Developer (Thưởng 20%)
class Developer : Employee
{
public Developer(string name, double salary) : base(name, salary) { }
public override double CalculateBonus()
{
return BaseSalary * 0.2;
}
}
// 3. Lớp con: Manager (Thưởng 30% + Cố định 5.000.000)
class Manager : Employee
{
public Manager(string name, double salary) : base(name, salary) { }
public override double CalculateBonus()
{
return (BaseSalary * 0.3) + 5000000;
}
}
class Program
{
static void Main(string[] args)
{
// Sử dụng đa hình: Gom tất cả nhân viên vào chung một danh sách Employee
List<Employee> companyStaff = new List<Employee>();
// Upcasting tự động
companyStaff.Add(new Employee("Bảo Vệ", 10000000));
companyStaff.Add(new Developer("Hieu Backend", 20000000));
companyStaff.Add(new Manager("Sếp Lớn", 40000000));
Console.WriteLine("--- DANH SACH THUONG CUOI NAM ---");
foreach (Employee emp in companyStaff)
{
// Cùng gọi 1 hàm, nhưng logic tính toán hoàn toàn khác nhau cho mỗi người
double bonus = emp.CalculateBonus();
Console.WriteLine($"- {emp.Name}: {bonus:N0} VND");
}
Console.ReadLine();
}
}
}
📝 Tóm tắt bài học
-
Đa hình giúp các đối tượng thuộc Lớp con khác nhau có thể thực thi cùng một phương thức theo những cách khác nhau.
-
Lớp cha dùng
virtualđể cấp phép ghi đè. Lớp con dùngoverrideđể viết lại logic. -
Dùng biến của Lớp cha để tham chiếu đến đối tượng Lớp con (Upcasting) giúp quản lý và mở rộng hệ thống cực kỳ dễ dàng mà không cần sửa code cũ.
🛠 Bài tập thực hành
Yêu cầu: Mô phỏng hệ thống gửi thông báo (Notification):
-
Tạo Lớp cha
Notificationcó thuộc tínhMessagevà phương thứcvirtual void Send(). TrongSend(), in ra"Gửi thông báo mặc định: [Message]". -
Tạo Lớp con
EmailNotificationkế thừaNotification, ghi đèSend()để in ra"Gửi Email: [Message]". -
Tạo Lớp con
SmsNotificationkế thừaNotification, ghi đèSend()để in ra"Gửi tin nhắn SMS: [Message]". -
Trong
Main, tạo mộtList<Notification>chứa cả 3 loại thông báo trên. Dùngforeachduyệt qua danh sách và gọi phương thứcSend().
All rights reserved