Từ khóa virtual và override
Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 21 trên 70.
Khi một lớp dẫn xuất kế thừa một phương thức, đôi khi bạn muốn thay đổi hoàn toàn cách phương thức đó hoạt động. Các từ khóa virtual và override phối hợp với nhau để giúp bạn thực hiện điều này một cách có kiểm soát.
Đánh dấu một phương thức là virtual trong lớp cơ sở để cho biết các lớp dẫn xuất có thể thay thế phần triển khai của phương thức đó:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Some sound");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}Từ khóa override trong lớp dẫn xuất cho trình biên dịch biết bạn đang chủ ý thay thế phương thức của lớp cơ sở. Nếu phương thức của lớp cơ sở không có virtual, bạn không thể dùng override.
Sức mạnh thực sự thể hiện khi bạn dùng tham chiếu đến lớp cơ sở để giữ một đối tượng thuộc lớp dẫn xuất:
Animal myPet = new Dog();
myPet.Speak(); // Đầu ra: Woof!Mặc dù myPet được khai báo là Animal, C# gọi phiên bản Dog của Speak(). Đây gọi là đa hình lúc chạy: phương thức thực sự được gọi phụ thuộc vào kiểu thực của đối tượng, không phải kiểu khai báo của biến.
Bạn vẫn có thể truy cập phần triển khai của lớp cơ sở bằng base.Speak() bên trong phương thức ghi đè nếu muốn mở rộng hành vi thay vì thay thế hoàn toàn.
Thử thách
DễHãy cùng xây dựng một hệ thống thông báo để minh họa cách các lớp dẫn xuất có thể ghi đè hoàn toàn hành vi của lớp cơ sở bằng từ khóa virtual và override, đồng thời cho thấy sức mạnh của tính đa hình lúc chạy.
Bạn sẽ tạo ba tệp để sắp xếp code:
Notification.cs: Định nghĩa lớpNotificationtrong không gian tênAlerts. Lớp cơ sở này đại diện cho một thông báo chung, có thuộc tínhMessage(chuỗi) và hàm khởi tạo để thiết lập thuộc tính đó. Thêm phương thứcvirtualcó tênSend(), trả về"Sending notification: {Message}".EmailNotification.cs: Định nghĩa lớpEmailNotificationtrong không gian tênAlerts, kế thừa từNotification. Thêm thuộc tínhRecipient(chuỗi) và hàm khởi tạo nhận cả thông báo lẫn người nhận (dùngbaseđể truyền thông báo). Ghi đè phương thứcSend()để trả về"Emailing {Recipient}: {Message}".SmsNotification.cs: Định nghĩa lớpSmsNotificationtrong không gian tênAlerts, cũng kế thừa từNotification. Thêm thuộc tínhPhoneNumber(chuỗi) và hàm khởi tạo nhận cả thông báo lẫn số điện thoại. Ghi đèSend()để trả về"SMS to {PhoneNumber}: {Message}".Program.cs: Trong tệp chính, tạo instance của cả ba loại thông báo bằng các giá trị đầu vào. Sau đó, minh họa tính đa hình lúc chạy bằng cách lưu cả ba vào các biến kiểuNotificationvà gọiSend()trên từng biến.
Bạn sẽ nhận bốn giá trị đầu vào:
- Nội dung thông báo
- Địa chỉ email của người nhận
- Số điện thoại để gửi SMS
In kết quả gọi Send() trên từng thông báo, được lưu dưới dạng tham chiếu đến lớp cơ sở:
{base notification Send() result}
{email notification Send() result}
{sms notification Send() result}Ví dụ, nếu các giá trị đầu vào là Meeting at 3pm, alice@email.com và 555-1234, kết quả sẽ là:
Sending notification: Meeting at 3pm
Emailing alice@email.com: Meeting at 3pm
SMS to 555-1234: Meeting at 3pmHãy chú ý rằng dù cả ba đều được lưu trong các biến kiểu Notification, mỗi biến vẫn gọi phiên bản Send() được ghi đè riêng của nó. Đó chính là tính đa hình lúc chạy!
Tự làm thử
using System;
using Alerts;
class Program
{
public static void Main(string[] args)
{
// Đọc các đầu vào
string message = Console.ReadLine();
string email = Console.ReadLine();
string phone = Console.ReadLine();
// TODO: Tạo instance của cả ba loại thông báo
// Lưu chúng vào các biến kiểu Notification để minh họa tính đa hình
// TODO: Tạo một Notification cơ sở
Notification baseNotification;
// TODO: Tạo một EmailNotification (lưu trong biến kiểu Notification)
Notification emailNotification;
// TODO: Tạo một SmsNotification (lưu trong biến kiểu Notification)
Notification smsNotification;
// TODO: Gọi Send() trên từng instance và in ra kết quả
// Nhờ tính đa hình khi chạy, chương trình sẽ gọi các phương thức ghi đè
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online