0

Bài 13: Tính Đa hình (Polymorphism) & Overriding

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

  1. Hiểu khái niệm Đa hình (Polymorphism) - Trụ cột thứ 3 của OOP.

  2. 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 đè).

  3. 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).

  1. Ở Lớp cha: Đặt từ khóa virtual và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é".

  2. Ở Lớp con: Đặt từ khóa override và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

  1. Đ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.

  2. Lớp cha dùng virtual để cấp phép ghi đè. Lớp con dùng override để viết lại logic.

  3. 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):

  1. Tạo Lớp cha Notification có thuộc tính Message và phương thức virtual void Send(). Trong Send(), in ra "Gửi thông báo mặc định: [Message]".

  2. Tạo Lớp con EmailNotification kế thừa Notification, ghi đè Send() để in ra "Gửi Email: [Message]".

  3. Tạo Lớp con SmsNotification kế thừa Notification, ghi đè Send() để in ra "Gửi tin nhắn SMS: [Message]".

  4. Trong Main, tạo một List<Notification> chứa cả 3 loại thông báo trên. Dùng foreach duyệt qua danh sách và gọi phương thức Send().


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í