1. Tóm tắt về sự khác biệt cổ điển
Nếu ai đó nói: "Interface chỉ là tập hợp các signature", hãy hỏi lại: "Bạn đang code trên version C# nào thế?" Từ C# 8 trở đi, interface mạnh lên rất nhiều. Đã đến lúc so sánh chúng với abstract class — không chỉ theo kiểu truyền thống mà còn xét cả các tính năng mới của .NET.
Nếu quay lại vài năm trước — thời C# 7, — mọi thứ khá đơn giản. Abstract class có thể định nghĩa field và method đã được implement một phần, còn interface chỉ có signature (method, property, event, indexer).
Kế thừa abstract class là quan hệ "là một" (Is-a), còn interface thì là đa kế thừa hành vi ("có thể làm", can-do).
| Đặc điểm | Abstract class | Interface (trước C# 8) |
|---|---|---|
| Quan hệ | is-a | can-do |
| Kế thừa | Chỉ một | Đa kế thừa |
| Field | Có thể chứa | Không thể |
| Implement method | Có thể | Không thể |
| Constructor | Có thể | Không thể |
| Access modifier | Đa dạng (public, protected, ...) | Chỉ mặc định public |
Như bạn thấy, trước đây abstract class đúng kiểu "anh cả" của interface — mạnh hơn và linh hoạt hơn. Nhưng mọi thứ thay đổi rồi!
2. Interface với default implementation
Từ C# 8 (và càng rõ rệt ở C# 14, .NET 9) interface có thêm siêu năng lực mới — method với default implementation, gọi là "Default Interface Methods" (DIM).
Trông nó như nào?
public interface IAnimal
{
void SayHello();
// Method có default implementation!
void Walk()
{
Console.WriteLine("Tôi đang đi...");
}
}
Wow! Đột nhiên interface có thể chứa code thực thi trong method. Không chỉ một mà bao nhiêu tuỳ thích. Nhưng có một lưu ý: các method này phải có body rõ ràng, còn lại (field, private method, constructor) — vẫn không được.
3. Tính năng hiện đại của interface
Những thứ mới mà dev .NET hiện đại nên biết:
- Method với default implementation.
- Private method trong interface (chỉ dùng nội bộ, chỉ method khác trong interface đó gọi được).
- Static method (từ C# 8).
- Property với default implementation.
- Static field (từ C# 14 — "static interface members").
- Abstract static member ("abstract static members" — giờ interface có thể bắt buộc implement static method!).
Ví dụ về interface hiện đại đầy đủ:
public interface ILogger
{
static int LoggerCount { get; set; } // C# 14
void Log(string message); // Signature (contract)
// Default implementation
void LogWarning(string warning)
{
Log("[CẢNH BÁO]: " + warning);
}
// Private helper method trong interface (C# 8+)
private void FormatAndLog(string level, string msg)
{
Log($"{level}: {msg}");
}
// Static method trong interface (C# 8+)
static void PrintLoggerInfo()
{
Console.WriteLine("Interface ILogger — trợ thủ đắc lực của bạn!");
}
}
Bạn thử tưởng tượng — trước đây chuyện này là bất khả thi, giống như cho mèo làm bảo vệ server vậy.
4. Abstract class: có gì mới?
Abstract class... nói sao nhỉ... không tiến hoá nhiều trong thập kỷ qua. Vẫn có thể chứa:
- Field (bao gồm private, protected, static).
- Method đã implement và abstract method.
- Constructor (đúng, có thể tạo abstract class với logic khởi tạo).
- Property, event, indexer.
- Static và instance member.
Ví dụ về abstract class:
public abstract class Animal
{
public string Name { get; set; }
public abstract void Speak();
public virtual void Walk()
{
Console.WriteLine($"{Name} đi bằng chân!");
}
protected void Eat()
{
Console.WriteLine($"{Name} ăn thức ăn.");
}
}
Abstract class vẫn là nơi tuyệt vời để chứa logic chung, trạng thái và hành vi cho cả hệ thống class con.
5. So sánh hiện đại: bảng với các tính năng mới
| Đặc điểm | Abstract class | Interface (C# 14+, .NET 9) |
|---|---|---|
| Quan hệ | is-a (là một) | can-do (có thể làm) |
| Kế thừa | Chỉ một | Đa kế thừa |
| Field | Có, bất kỳ | Chỉ static* (C# 14+) |
| Constructor | Có | Không |
| Implement method | Có (virtual/abstract) | Có (default, static, abstract static) |
| Property có implementation | Có | Có (default implementation) |
| Private member | Có | Có (chỉ method, C# 8+) |
| Static member | Có | Có (C# 8+, có giới hạn) |
| Static field | Có | Có * (C# 14+) |
| Access modifier | Bất kỳ | Mặc định public hoặc private |
* — Static field trong interface thường chỉ dùng cho trường hợp đặc biệt, và đây là tính năng rất mới của ngôn ngữ.
6. Dùng interface hay abstract class: khuyến nghị hiện đại
Interface (giờ còn có default implementation) — là công cụ tạo contract giữa các thành phần. Điểm mạnh: đa dạng. Class của bạn có thể implement cả chục interface, thành ra siêu đa năng.
Abstract class vẫn là lựa chọn nếu:
- Cần trạng thái chung (field), logic và hành vi mà các class con kế thừa.
- Cần logic mặc định nhưng có thể override (dùng virtual).
- Muốn khởi tạo tập trung qua constructor.
Trong thực tế, thường gặp mô hình này: "contract thuần" thì để trong interface, còn nếu cần code chung hoặc hạ tầng cho subclass — tạo abstract base class.
. ┌────────────────────────┐
│ Interface │
│ (contract: có thể gì) │
└─────────┬──────────────┘
│
┌──────────────┼──────────────┐
│ │ │
Implementation 1 Implementation 2 ... Implementation N
MyLogger CloudLogger FileLogger
(có thể kết hợp với kế thừa abstract class)
7. Tình huống — khi nào cái nào thắng
Đa implement:
Giả sử bạn có interface IDrivable và abstract class Vehicle. Giờ class Car có thể kế thừa base — Vehicle — và đồng thời implement nhiều interface (IDrivable, IRepairable, IInsurable). Nếu bạn dùng abstract class Repairable, phải chọn — hoặc Vehicle, hoặc Repairable! Interface rõ ràng thắng thế.
Logic và trạng thái chung:
Giả sử mọi "phương tiện" đều có field "biển số". Cái này phải là field của abstract class. Interface không có field (trừ static).
Tiến hoá API:
Một trong những điểm đột phá của Default Interface Methods — giờ có thể mở rộng interface mà không sợ phá vỡ code cũ.
Ví dụ, thêm method mới vào interface với default implementation — mọi thứ vẫn chạy, các implement cũ không bị lỗi! Trước đây thì đau đầu (hoặc không thể).
8. Ví dụ thực tế
Trong app học tập của mình, ta sẽ thêm logging. Tạo interface ILogger với default implementation:
public interface ILogger
{
void Log(string message);
// Default implementation cho mọi implement!
void LogInfo(string info)
{
Log("[INFO] " + info);
}
// Static method của interface
static void PrintHelp()
{
Console.WriteLine("Dùng ILogger để log sự kiện");
}
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
// Ở đâu đó trong code:
ILogger logger = new ConsoleLogger();
logger.LogInfo("Hệ thống đã khởi động!"); // chạy nhờ default implementation
// Gọi static method của interface
ILogger.PrintHelp();
Nếu bạn thêm method mới với default implementation vào interface, mọi implement hiện có (ví dụ ConsoleLogger) tự động có method mới — không lo bug hay lỗi code.
9. Lỗi và lưu ý: thực tế và bẫy
Cần biết là không phải lúc nào cũng màu hồng như tưởng. Ví dụ, nếu interface có default implementation, nhưng bạn gọi object qua kiểu class chứ không phải kiểu interface, default implementation chỉ dùng được qua interface thôi.
ConsoleLogger log = new ConsoleLogger();
log.LogInfo("Hello"); // Không compile: LogInfo không có trong class!
ILogger log2 = log;
log2.LogInfo("Hello"); // OK luôn!
Nó giống như trường hợp implement interface explicit. Đôi khi tiện để ẩn bớt API, đôi khi lại gây bất ngờ cho newbie.
GO TO FULL VERSION