Đa hình lúc biên dịch và lúc chạy
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 25 trên 70.
Đa hình nghĩa là “nhiều hình thức”, tức khả năng cùng một đoạn code hoạt động khác nhau tùy theo ngữ cảnh. C# hỗ trợ hai kiểu đa hình riêng biệt, và hiểu rõ thời điểm mỗi kiểu được xác định sẽ giúp bạn viết code linh hoạt hơn.
Đa hình tại thời điểm biên dịch (còn gọi là đa hình tĩnh) được trình biên dịch xử lý trước khi chương trình chạy. Nạp chồng phương thức là ví dụ chính: nhiều phương thức dùng chung tên nhưng có các tham số khác nhau:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // Trình biên dịch chọn phiên bản int
calc.Add(5.0, 3.0); // Trình biên dịch chọn phiên bản doubleTrình biên dịch (compiler) kiểm tra kiểu dữ liệu của các đối số (argument) và chọn phương thức (method) phù hợp ngay khi biên dịch (compile time). Quyết định này được cố định trước khi chương trình chạy.
Đa hình thời gian chạy (đa hình động) được phân giải khi chương trình chạy. Điều này xảy ra với các phương thức virtual và override, kiểu thực tế của đối tượng sẽ quyết định phương thức nào được thực thi:
Animal pet = new Dog(); // Khai báo là Animal, thực tế là Dog
pet.Speak(); // Gọi phương thức Speak() của Dog khi chạyTrình biên dịch không biết Speak() nào sẽ chạy. Quyết định đó được đưa ra trong thời gian chạy, dựa trên kiểu thực sự của đối tượng. Đây là điều khiến tính đa hình dựa trên kế thừa trở nên mạnh mẽ, giúp xây dựng các hệ thống linh hoạt và dễ mở rộng.
Thử thách
DễHãy xây dựng một hệ thống chuyển đổi để minh họa cả hai kiểu đa hình. Bạn sẽ tạo một lớp dùng nạp chồng phương thức (đa hình tại thời điểm biên dịch), cùng với hệ thống phân cấp kế thừa dùng các phương thức virtual/override (đa hình tại thời điểm chạy).
Bạn sẽ sắp xếp code trong bốn tệp:
Converter.cs: Định nghĩa lớpConvertertrong không gian tênConversion. Lớp này minh họa đa hình tại thời điểm biên dịch thông qua nạp chồng phương thức. Tạo ba phương thứcConvertđược nạp chồng:Convert(int value)trả về"Integer: {value}"Convert(double value)trả về"Double: {value}"Convert(string value)trả về"String: {value}"
Shape.cs: Định nghĩa lớp cơ sởShapetrong không gian tênConversion. Lớp này có thuộc tínhName(chuỗi) và hàm khởi tạo gán giá trị cho thuộc tính đó. Thêm phương thứcvirtualcó tênDescribe(), trả về"This is a {Name}".Circle.cs: Định nghĩa lớpCircletrong không gian tênConversion, kế thừa từShape. Thêm thuộc tínhRadius(số thực). Hàm khởi tạo nhận bán kính và truyền"Circle"cho hàm khởi tạo của lớp cơ sở. Ghi đèDescribe()để trả về"This is a Circle with radius {Radius}".Program.cs: Trong tệp chính, minh họa cả hai kiểu đa hình. Trước tiên, tạo mộtConvertervà gọi cả ba phương thức được nạp chồng với các giá trị đầu vào. Sau đó, tạo mộtCircle, lưu đối tượng đó vào một biến kiểuShapevà gọiDescribe()để thể hiện đa hình tại thời điểm chạy.
Bạn sẽ nhận bốn giá trị đầu vào:
- Một giá trị số nguyên
- Một giá trị số thực kiểu double
- Một giá trị chuỗi
- Bán kính hình tròn (double)
In đầu ra theo định dạng sau:
Compile-time Polymorphism:
{Convert(int) result}
{Convert(double) result}
{Convert(string) result}
Runtime Polymorphism:
{Describe() result from Shape variable holding Circle}Ví dụ, nếu đầu vào là 42, 3.14, Hello và 5.5, đầu ra sẽ là:
Compile-time Polymorphism:
Integer: 42
Double: 3.14
String: Hello
Runtime Polymorphism:
This is a Circle with radius 5.5Lưu ý điểm khác biệt chính: trình biên dịch quyết định sẽ gọi phương thức Convert nào dựa trên kiểu đối số tại thời điểm biên dịch, còn phương thức Describe() được xác định tại thời điểm chạy dựa trên kiểu thực tế của đối tượng, dù đối tượng đó được lưu trong một biến Shape!
Tự làm thử
using System;
using Conversion;
class Program
{
public static void Main(string[] args)
{
// Đọc các giá trị đầu vào
int intValue = Convert.ToInt32(Console.ReadLine());
double doubleValue = Convert.ToDouble(Console.ReadLine());
string stringValue = Console.ReadLine();
double radius = Convert.ToDouble(Console.ReadLine());
// TODO: Minh họa tính đa hình tại thời điểm biên dịch
// Tạo đối tượng Converter và gọi cả ba phương thức Convert được nạp chồng
Console.WriteLine("Compile-time Polymorphism:");
// Gọi Convert với intValue, doubleValue và stringValue
// TODO: Minh họa tính đa hình tại thời điểm chạy
// Tạo đối tượng Circle và lưu vào biến Shape
// Sau đó gọi Describe() qua biến Shape
Console.WriteLine("Runtime Polymorphism:");
// Tạo Circle và gọi Describe()
}
}
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