1. Giới thiệu
Bình thường, khi bạn triển khai một interface, bạn chỉ cần tạo các method public trong class của mình, với signature giống như trong interface. Cái này gọi là triển khai ngầm định hoặc triển khai public. Compiler đủ thông minh để hiểu: "À, method DoSomething() trong class MyClass là để triển khai cho IDoable.DoSomething()!".
Nhưng nếu:
- Class của bạn SmartDevice triển khai interface ICamera (có method TakePicture()) và interface IScreen (cũng có method TakePicture() — để chụp screenshot)?
- Hoặc class Robot đã có method public Reset() để reset toàn bộ hệ thống, nhưng bạn muốn nó cũng triển khai interface IDevice với method Reset(), chỉ reset một phần cài đặt?
Trong mấy trường hợp này sẽ có sự mơ hồ hoặc bạn muốn tách biệt rõ ràng chức năng. Đây là lúc triển khai interface tường minh xuất hiện.
Triển khai interface tường minh cho phép bạn nói với compiler: "Method này là để triển khai chính xác interface này, và nó chỉ truy cập được qua reference của interface đó thôi". Kiểu như Peter Parker chỉ bắn tơ nhện khi đóng vai Spider-Man, còn kỹ năng nhiếp ảnh thì khi là "Peter Parker bình thường".
2. Khi nào và tại sao cần triển khai interface tường minh
Khi bạn triển khai interface theo cách thông thường, các method và property của nó sẽ trở thành một phần public interface của class bạn. Nhưng đôi khi bạn muốn chúng chỉ truy cập được qua interface, không phải trực tiếp qua class. Điều này xảy ra thường xuyên hơn bạn nghĩ! Ví dụ, nếu hai interface yêu cầu cùng một method (nhưng ý nghĩa khác nhau), hoặc bạn muốn giới hạn quyền truy cập chỉ cho ai dùng object qua interface.
Đây là lúc triển khai interface tường minh phát huy tác dụng. Nó giống như một lối đi bí mật trong kiến trúc phần mềm: bên ngoài không ai thấy, nhưng ai biết thì đi được!
Tình huống 1: Xung đột tên
Giả sử bạn có một class triển khai hai interface, cả hai đều yêu cầu method cùng tên nhưng logic hoàn toàn khác nhau. Ví dụ:
interface IWriter
{
void Print();
}
interface IPrinter
{
void Print();
}
Bạn muốn IWriter.Print() ghi text vào file, còn IPrinter.Print() gửi text ra máy in. Nếu chỉ triển khai method Print() bình thường thì không tách biệt được hành vi. Đây là lúc triển khai tường minh cứu nguy.
Tình huống 2: Ẩn method interface không cần thiết/kỹ thuật
Có lúc class của bạn bắt buộc phải triển khai một method interface, nhưng bạn không muốn ai cũng dùng method đó (ví dụ, thành viên interface chỉ dùng cho nội bộ hệ thống).
Tình huống 3: Bảo vệ khỏi gọi nhầm
Nếu method interface không dành cho gọi trực tiếp (ví dụ, cơ chế internal của framework), bạn có thể triển khai tường minh — lúc này dev khác sẽ không thể gọi trực tiếp qua object class.
3. Cú pháp triển khai interface tường minh
Điểm khác biệt chính — khi triển khai tường minh, bạn đặt tên method/property với đầy đủ tên interface. Không có modifier (public/private) hay từ khóa override!
Cú pháp chung:
Kiểu_trả_về TênInterface.TênMethod(tham_số)
{
// triển khai
}
Nhìn như kiểu bạn chỉ rõ tên interface để compiler và đồng đội không nhầm lẫn method này thuộc contract nào.
Giải quyết xung đột tên
Cùng xem lại ví dụ với IWriter và IPrinter. Ta tiếp tục phát triển app học tập, giả sử có class báo cáo:
interface IWriter
{
void Print();
}
interface IPrinter
{
void Print();
}
public class Report : IWriter, IPrinter
{
// Triển khai tường minh IWriter.Print
void IWriter.Print()
{
Console.WriteLine("Lưu báo cáo vào file (Writer)...");
}
// Triển khai tường minh IPrinter.Print
void IPrinter.Print()
{
Console.WriteLine("Gửi báo cáo ra máy in giấy (Printer)...");
}
// Method bổ sung cho hiển thị người dùng
public void Show()
{
Console.WriteLine("Hiển thị báo cáo trên màn hình.");
}
}
Giờ thử dùng các interface khác nhau:
var report = new Report();
report.Show(); // Method public bình thường
// report.Print(); // Lỗi! Không có method Print trong class Report
IWriter writer = report;
writer.Print(); // Gọi triển khai IWriter.Print()
IPrinter printer = report;
printer.Print(); // Gọi triển khai IPrinter.Print()
Ở đây các method Print không truy cập được qua object report, chỉ qua interface phù hợp. Đây là cốt lõi của triển khai tường minh: chỉ qua "cổng" interface mới gọi được method cần.
Trong bộ nhớ nó trông thế nào: minh họa đơn giản
Thực chất, triển khai tường minh "giấu" thành viên interface trong class. Hình dung như bảng sau:
| Cách gọi | Thực tế gọi gì |
|---|---|
|
Lỗi biên dịch — không có method này |
|
Triển khai tường minh |
|
Triển khai tường minh |
|
Method Show của class |
4. Ví dụ: Interface — chỉ cho hạ tầng
Trong dự án thực tế hay gặp kiểu này: class phải triển khai một interface kỹ thuật nào đó, nhưng triển khai này không cần cho người dùng bình thường của class.
interface IBroadcastable
{
void Broadcast();
}
public class SecretMessage : IBroadcastable
{
void IBroadcastable.Broadcast()
{
Console.WriteLine("Tin nhắn bí mật đã phát sóng...");
}
public void Reveal()
{
Console.WriteLine("Hiển thị bí mật trên màn hình.");
}
}
// Trong code bình thường:
var message = new SecretMessage();
message.Reveal(); // Method cho người dùng
// message.Broadcast(); // Lỗi! — không có method này
// Chỉ hạ tầng biết cách dùng:
((IBroadcastable)message).Broadcast();
Ở đây method Broadcast() — chỉ cho ai làm việc qua contract interface.
5. Triển khai tường minh property và indexer
Không chỉ method, bạn còn có thể triển khai tường minh property và cả indexer.
interface IDescribable
{
string Description { get; }
}
public class Product : IDescribable
{
// Triển khai tường minh property
string IDescribable.Description => "Mô tả chỉ truy cập qua interface";
// Property public bình thường
public string Name { get; set; }
}
// Ví dụ sử dụng:
var p = new Product { Name = "Thiết bị" };
// p.Description; // Lỗi! Không có property này trong Product
var descr = ((IDescribable)p).Description;
Console.WriteLine(descr);
6. Một số lưu ý hay ho
Kế thừa với triển khai tường minh hoạt động ra sao
Khi kế thừa, mọi thứ khá trực quan: nếu class cha triển khai interface tường minh, class con cũng "kế thừa" hành vi đó. Nhưng nếu class con muốn override method interface đã triển khai tường minh thì không được: triển khai tường minh không thể là virtual. Tức là không override được thành viên đã triển khai tường minh.
Nếu bạn cần override — phải dùng triển khai thường hoặc cân nhắc pattern "template method".
Triển khai tường minh và ngầm định
+----------------+
| Invoice |
+----------------+
| Show() | // Gọi trực tiếp được
+----------------+
| ITxtExportable.Export() // Chỉ qua ITxtExportable
| IJsonExportable.Export() // Chỉ qua IJsonExportable
+----------------+
Đặc điểm/hạn chế của triển khai tường minh
- Method/property triển khai tường minh không có modifier truy cập. Mặc định là private với bên ngoài và chỉ truy cập qua interface.
- Không thể khai báo static, virtual, abstract hoặc override cho thành viên này.
- Không thể gọi thành viên đã triển khai tường minh qua object class trực tiếp (chỉ qua biến kiểu interface).
- Nếu interface kế thừa interface khác, bạn có thể triển khai tường minh thành viên ở bất kỳ cấp nào trong hierarchy.
7. Ưu điểm của triển khai tường minh
Triển khai tường minh không chỉ là mẹo cú pháp để giải quyết xung đột. Nó có nhiều lý do chính đáng để tồn tại:
Giải quyết xung đột tên (The Big One): Đây là lý do chính và rõ ràng nhất. Nếu hai interface bạn triển khai đều khai báo method/property cùng signature, triển khai tường minh cho phép bạn cung cấp hai triển khai riêng biệt cho từng interface. Nếu không sẽ bị mơ hồ.
Ví dụ thực tế: Giả sử bạn có máy in kiêm máy scan. Interface IPrinter có method Print(), interface IScanner có method Scan(). Nhưng nếu cả hai đều có method ProcessDocument()? Triển khai tường minh cho phép bạn làm IPrinter.ProcessDocument() để in, IScanner.ProcessDocument() để scan, và chúng hoạt động khác nhau.
Ẩn chi tiết triển khai, làm sạch API class: Method triển khai tường minh không phải là public API của class. Chỉ truy cập được khi ép kiểu object sang interface. Rất hữu ích khi bạn muốn một số chức năng chỉ "theo hợp đồng", không phải hành vi chung của object.
Ví dụ: Bạn tạo một công cụ tài chính phức tạp, ví dụ CreditCard. Nó có thể triển khai IPayable (để thanh toán) và IAdminConfigurable (cho cài đặt nội bộ, như đặt hạn mức). Method IAdminConfigurable.SetLimit() không nên cho ai cũng dùng, chỉ hệ thống admin làm việc với CreditCard như IAdminConfigurable mới được. Triển khai tường minh giúp SetLimit() ẩn khỏi public API của CreditCard, làm API class sạch và an toàn hơn.
Đảm bảo contract: Đôi khi method trong class bạn tình cờ trùng signature với method trong interface, nhưng bạn không muốn nó là triển khai của interface. Ví dụ, bạn có class MyList với method Clear() để xóa trạng thái nội bộ. Nếu bạn quyết định MyList triển khai IList<T> (cũng có Clear()), mặc định MyList.Clear() sẽ là triển khai của IList<T>.Clear(). Nếu logic khác nhau, triển khai tường minh IList<T>.Clear() giúp bạn tách biệt chúng.
Triển khai ngầm định (Implicit) vs. tường minh (Explicit)
Để dễ hình dung, cùng xem bảng so sánh nhỏ:
| Đặc điểm | Triển khai ngầm định (Implicit) | Triển khai tường minh (Explicit) |
|---|---|---|
| Truy cập | Truy cập được qua class và interface. | Chỉ qua interface. |
| Modifier truy cập | Thường là public (hoặc protected ...). | Không có modifier truy cập (không thể public). |
| Cú pháp | |
|
| Giải quyết xung đột | Không giải quyết, method dùng cho mọi interface có cùng signature. | Giải quyết xung đột, cho phép nhiều triển khai khác nhau. |
| Làm sạch API class | Method là một phần public API của class. | Method không phải public API của class. |
| Mục đích | Cho các triển khai interface chung, rõ ràng. | Cho giải quyết xung đột hoặc ẩn logic đặc biệt. |
Như bạn thấy, đây là hai mặt của một vấn đề, mỗi cái có mục đích riêng. Phần lớn bạn sẽ dùng triển khai ngầm định vì nó đơn giản, tiện lợi. Triển khai tường minh là công cụ cho các trường hợp đặc biệt nhưng quan trọng.
8. Lỗi thường gặp khi triển khai interface tường minh
Lỗi #1: method không thấy qua object class.
Method triển khai tường minh không truy cập được qua instance class trực tiếp. Nhiều bạn nhầm: compiler báo không tìm thấy method, dù nó có — chỉ là "bị giấu". Cách giải quyết: ép kiểu object sang interface, ví dụ: (IMyInterface)obj.Method().
Lỗi #2: dùng sai modifier truy cập.
Khi triển khai tường minh không được dùng modifier như public hay override. Nếu thử sẽ bị lỗi biên dịch — compiler không chấp nhận.
Lỗi #3: cố override method đã triển khai tường minh ở class con.
Nếu method interface đã triển khai tường minh ở class cha, không override được ở class con. Hạn chế này cần nhớ khi thiết kế hierarchy class với interface.
GO TO FULL VERSION