1. Interface là gì?
Nếu abstract class là một kiểu "bán thành phẩm" với một phần đã được hiện thực, thì interface chỉ đơn giản là một danh sách yêu cầu: cái gì phải có ở một thực thể (class) để nó có thể được dùng trong một ngữ cảnh trừu tượng nào đó.
Trong lập trình, interface giống như một "hợp đồng" hay "danh sách yêu cầu" về hành vi của object. Nó mô tả tập hợp các phương thức public, thuộc tính, indexer và event mà class implement interface đó bắt buộc phải cung cấp. Interface nói: "Nếu mày muốn được gọi là IDriveable (có thể lái được), thì làm ơn cung cấp các phương thức Drive() và Stop()".
Bạn có thể tưởng tượng interface như Danh Sách Yêu Cầu Đối Với Nhân Viên: ví dụ, nếu bạn muốn tuyển "Đầu bếp", thì trong interface sẽ ghi: "Phải biết nấu ăn, phục vụ món, rửa tay". Còn đầu bếp cụ thể giải quyết các nhiệm vụ đó thế nào là chuyện của họ. Quan trọng là bên ngoài phải làm đúng hợp đồng.
Lưu ý: interface chỉ mô tả cái gì class phải cung cấp, chứ không phải cách làm điều đó.
Tóm tắt theo OOP
- Interface mô tả "giao diện" (API) bên ngoài của object: cho phép làm những gì.
- Không chứa trạng thái (không có field). Theo truyền thống, interface cũng không chứa hiện thực, nhưng trong các phiên bản C# mới đã có ngoại lệ (ví dụ default method), cái này sẽ nói kỹ sau.
- Một class có thể implement bao nhiêu interface cũng được (khác với kế thừa class!).
So sánh thực tế
Cổng USB — ai cũng biết có thể cắm chuột, bàn phím, USB, tai nghe, thậm chí cả máy pha cà phê (đúng đấy!). Bên trong "con chuột" thế nào không quan trọng, miễn là có cổng USB và thiết bị hỗ trợ đúng protocol. Đó chính là interface!
Interface để làm gì?
- Giảm sự phụ thuộc. Code làm việc với interface, không phải class cụ thể. Lập trình "ở mức interface".
- Cho phép kế thừa đa hành vi: class có thể implement nhiều interface.
- Chuẩn hóa: có thể tạo các cơ chế dùng chung: ví dụ, mọi object có thể so sánh đều implement interface IComparable.
- Dễ test: dễ dàng thay thế hiện thực cụ thể bằng "mock" khi test.
- Hỗ trợ plugin: có thể thêm module mới mà không phải sửa code cũ.
2. Cú pháp khai báo interface
Giờ đến code nhé! Trong C#, interface được khai báo bằng từ khóa interface, và tên interface thường bắt đầu bằng I :
// Khai báo interface
public interface IPrintable
{
// Phương thức abstract — hợp đồng
void Print();
// Có thể khai báo thuộc tính
string Name { get; set; }
}
Nhớ nhé: phương thức và thuộc tính trong interface KHÔNG có hiện thực (không có thân hàm, chỉ có signature — giống như abstract method).
Đặc điểm chính của interface
- Không được khai báo field (biến thành viên) trong interface.
- Tất cả method, property, event và indexer mặc định là public (và khi implement cũng phải là public).
- Interface không thể có constructor (vì nó không có trạng thái).
- Interface không quan tâm class implement nó là abstract hay cụ thể.
Interface trong thực tế
Cùng tích hợp interface vào app học tập của mình nhé. Giả sử giờ ta có interface "có thể in" (IPrintable), được implement bởi class Report và ví dụ, class mới Invoice.
public interface IPrintable
{
void Print();
string Name { get; set; }
}
Giờ định nghĩa class implement interface này:
public class Report : IPrintable
{
public string Name { get; set; }
public Report(string name)
{
Name = name;
}
// Hiện thực method từ interface
public void Print()
{
Console.WriteLine($"In báo cáo: {Name}");
}
}
Và đây là một class hoàn toàn khác, nhưng cũng dùng interface đó:
public class Invoice : IPrintable
{
public string Name { get; set; }
public Invoice(string name)
{
Name = name;
}
public void Print()
{
Console.WriteLine($"In hóa đơn: {Name}");
}
}
Giờ ta có thể viết một method làm việc với bất kỳ thực thể nào có thể in:
public static void PrintAnything(IPrintable printable)
{
printable.Print(); // Xong! Không quan trọng là báo cáo hay hóa đơn — miễn là in được.
}
Ví dụ sử dụng:
var report = new Report("Báo cáo hàng tháng");
var invoice = new Invoice("Hóa đơn #12345");
PrintAnything(report); // In báo cáo: Báo cáo hàng tháng
PrintAnything(invoice); // In hóa đơn: Hóa đơn #12345
Đó, interface giúp code của bạn vừa linh hoạt, vừa mở rộng, lại đẹp nữa.
3. Implement interface
Kết nối interface với class
Interface được implement bởi class bằng dấu hai chấm (giống như khi kế thừa):
public class Ticket : IPrintable
{
public string Name { get; set; }
public void Print()
{
Console.WriteLine($"In vé: {Name}");
}
}
Lưu ý: class bắt buộc phải implement tất cả member của interface. Các member implement phải là public.
Nếu thiếu bất kỳ member nào:
public class BrokenTicket : IPrintable
{
// Thiếu hiện thực Print()
public string Name { get; set; }
}
// Lỗi biên dịch: 'BrokenTicket' chưa implement member 'IPrintable.Print()'
Nhiều interface
Class có thể implement nhiều interface, cách nhau bằng dấu phẩy:
public interface IStorable
{
void Store();
}
public class MultiPurposeDoc : IPrintable, IStorable
{
public string Name { get; set; }
public void Print()
{
Console.WriteLine("In tài liệu");
}
public void Store()
{
Console.WriteLine("Lưu tài liệu");
}
}
4. Interface để làm gì?
Có thể giờ bạn đang nghĩ: "Ờ, cú pháp thì hiểu rồi. Nhưng thực tế thì để làm gì, ngoài việc làm khó mình ở môn này?" Đừng lo! Interface là một trong những công cụ được dùng nhiều nhất trong lập trình chuyên nghiệp.
Phân chia trách nhiệm và giảm phụ thuộc (Decoupling / Loose Coupling):
- Giả sử bạn đang làm một trình phát nhạc. Nó không quan tâm nhạc lấy từ đâu — file local, internet hay CD. Nó chỉ cần nguồn nhạc cung cấp được audio stream.
- Bạn có thể định nghĩa interface IAudioSource với method GetAudioStream().
- Sau đó bạn có các class FileAudioSource, InternetAudioSource, CDAudioSource implement interface này.
- Trình phát nhạc sẽ làm việc với IAudioSource, không cần biết loại cụ thể. Nếu mai có thêm nguồn mới, ví dụ BluetoothAudioSource, bạn không phải sửa code player! Chỉ cần tạo class mới implement IAudioSource. Như vậy hệ thống của bạn cực kỳ linh hoạt và dễ mở rộng. Đó là giảm phụ thuộc – các thành phần phụ thuộc vào abstraction (interface), không phải hiện thực cụ thể.
Đa hình và xử lý đồng nhất:
Như ví dụ với PrintAnything, bạn có thể có nhiều object khác loại, nhưng cùng chung hành vi mô tả bởi interface. Bạn gọi cùng một method (Print()) cho tất cả, không cần biết cụ thể là báo cáo, hóa đơn hay vé. Điều này giúp code cực kỳ ngắn gọn và linh hoạt.
Test (Unit Testing):
Đây có lẽ là ứng dụng quan trọng nhất của interface. Khi bạn test một thành phần nào đó, nó thường cần các thành phần khác để chạy (ví dụ class lưu dữ liệu có thể phụ thuộc vào class làm việc với database).
Thay vì truyền vào class thật DatabaseSaver (cần database thật để test!), bạn có thể truyền vào object "giả" (hoặc "mock"), chỉ cần implement interface IDataSaver. Object "mock" này chỉ giả lập lưu dữ liệu, không động đến database thật. Nhờ vậy bạn test từng phần riêng biệt, nhanh và không phụ thuộc bên ngoài.
Phát triển API và framework:
Khi bạn tạo thư viện hoặc framework, bạn muốn cung cấp "điểm mở rộng" cho dev khác. Interface cực kỳ phù hợp. Bạn chỉ cần nói: "Nếu muốn component của bạn làm việc với hệ thống của tôi, hãy implement interface này". Thư viện chuẩn .NET đầy interface (ví dụ IEnumerable<T>, IDisposable, IComparable<T>) – chúng định nghĩa hợp đồng cho các tình huống phổ biến nhất.
Lập trình ở mức interface (Programming to an Interface):
Dev kinh nghiệm hay nói: "Lập trình ở mức interface, không phải hiện thực". Tức là khi bạn định nghĩa kiểu biến hoặc tham số method, thay vì dùng class cụ thể (Car), hãy dùng interface (IDriveable). Như vậy code của bạn linh hoạt hơn, ít phụ thuộc vào chi tiết hiện thực, dễ thay đổi hiện thực này sang hiện thực khác.
5. Lỗi phổ biến khi làm việc với interface
Lỗi #1: cố tạo instance của interface.
Bạn có thể viết Cat murzik = new Cat("Murzik", 3);, vì Cat là class cụ thể. Nhưng bạn không thể viết ITalkable talker = new ITalkable();. Interface chỉ là hợp đồng, mẫu. Nó không có hiện thực và không thể tạo trực tiếp. Nó giống như bản vẽ, không phải ngôi nhà thật.
Lỗi #2: quên implement tất cả member của interface.
Nếu bạn khai báo class implement interface, ví dụ IMyInterface, thì bắt buộc phải implement tất cả method của nó. Thiếu một method cũng báo lỗi biên dịch: MyClass chưa implement IMyInterface.TheMissingMethod().
Lỗi #3: sai modifier khi implement.
Method trong interface ngầm định là public, và khi implement cũng phải là public. Nếu bạn để private hoặc protected, compiler sẽ báo lỗi. Đã hứa thì phải hiện thực công khai.
Lỗi #4: cố thêm field hoặc constructor vào interface.
Interface mô tả hành vi, không phải trạng thái. Vì vậy không được thêm field hoặc constructor vào interface. Nếu thử — sẽ bị lỗi biên dịch. Chỉ được phép có property, mà cũng chỉ là mô tả getter/setter.
Lỗi #5: nhầm giữa override và implement interface.
Từ khóa override dùng để ghi đè method của base class. Nhưng khi implement interface thì không cần — chỉ cần viết method public với đúng signature. Đây là điểm dễ nhầm lẫn.
GO TO FULL VERSION