CodeGym /Các khóa học /C# SELF /Triển khai nhiều interface

Triển khai nhiều interface

C# SELF
Mức độ , Bài học
Có sẵn

1. Tại sao lại triển khai nhiều interface?

Khi bạn thiết kế một hệ thống thực tế, các object thường không chỉ thực hiện một "vai trò" — mà là nhiều vai trò cùng lúc. Hãy tưởng tượng: bạn có một cuốn sách điện tử, không chỉ đọc được mà còn chỉnh sửa được, thậm chí lưu lên cloud luôn. Theo ngôn ngữ lập trình hướng đối tượng (OOP), nghĩa là object đó phải triển khai nhiều interface cùng lúc:

  • IReadable — đọc nội dung.
  • IWritable — chỉnh sửa nội dung.
  • ISyncable — đồng bộ với kho lưu trữ từ xa.

Chính ở đây, khả năng triển khai nhiều interface mới phát huy tác dụng. Đây là siêu năng lực của C# (và OOP nói chung), mà class không có: bạn không thể kế thừa từ hai class trở lên, nhưng interface thì muốn bao nhiêu cũng được.

So sánh với đời thực

Hãy tưởng tượng một nhân viên trong công ty. Thằng Vasia có thể vừa là:

  • Lập trình viên (viết code)
  • Tester (thỉnh thoảng kiểm tra code của người khác)
  • Quản lý (lên kế hoạch sprint hoặc định mức cà phê mỗi ngày)

Những "vai trò" này có trách nhiệm hoàn toàn khác nhau. Nhưng Vasia vẫn cân được hết các "hợp đồng"! Trong lập trình cũng vậy: class triển khai interface và nhận trách nhiệm của chúng.

2. Cú pháp triển khai nhiều interface

Cực kỳ đơn giản: khi khai báo class thì liệt kê chúng bằng dấu phẩy:


public interface IReadable
{
    void Read();
}

public interface IWritable
{
    void Write(string text);
}

public class Note : IReadable, IWritable
{
    private string content = "";

    public void Read()
    {
        Console.WriteLine("Ghi chú: " + content);
    }

    public void Write(string text)
    {
        content = text;
        Console.WriteLine("Ghi chú đã được cập nhật!");
    }
}

Trong ví dụ này, class Note triển khai cả hai interface. Nghĩa là nó bắt buộc phải có cả hai method: ReadWrite.

3. Ví dụ trong ứng dụng "của chúng ta"

Trong các ví dụ trước, tụi mình phát triển một app ngân hàng đơn giản để làm việc với tài khoản. Giờ giả sử: cần một class tài liệu đa năng, có thể in ra (IPrintable), lưu vào file (ISavable) và có thể gửi qua e-mail (IEmailable).

Định nghĩa interface:


public interface IPrintable
{
    void Print();
}

public interface ISavable
{
    void Save(string filePath);
}

public interface IEmailable
{
    void Email(string toAddress);
}

Class triển khai tất cả cùng lúc:


public class Statement : IPrintable, ISavable, IEmailable
{
    public string Content { get; set; }

    public void Print()
    {
        Console.WriteLine("In sao kê...");
        Console.WriteLine(Content);
    }

    public void Save(string filePath)
    {
        // Dùng File.WriteAllText từ thư viện chuẩn.
        File.WriteAllText(filePath, Content);
        Console.WriteLine($"Sao kê đã lưu vào file: {filePath}");
    }

    public void Email(string toAddress)
    {
        Console.WriteLine($"Sao kê đã gửi tới email: {toAddress} (giả lập)");
    }
}

Giờ class này có thể dùng trong code kiểu gì cũng được:


var stat = new Statement { Content = "Giao dịch tháng: +1000 đv., -500 kd." };
stat.Print();
stat.Save("statement.txt");
stat.Email("boss@bank.corp");

À, thực tế rất tiện khi truyền object này vào các method yêu cầu interface cụ thể, không cần quan tâm nó còn làm được gì khác. Ví dụ, method in có thể nhận tham số IPrintable, và không cần biết bên trong còn là "lưu trữ" hay "gửi mail".

4. Dùng reference interface: "thấy" object làm được gì ở đâu?

Đây là lúc có phép màu. Khi bạn triển khai nhiều interface, bạn có thể truy cập object chỉ như một trong các "hợp đồng" của nó. Ví dụ:


IPrintable printable = new Statement { Content = "Bài giảng về trừu tượng hóa" };
printable.Print(); // Chỉ in được thôi

// printable.Save("file.txt"); // Lỗi: interface IPrintable không biết gì về Save.

Nhưng nếu chuyển sang interface khác:


ISavable savable = printable as ISavable;
if (savable != null)
{
    savable.Save("file.txt");
}

Cách này rất tiện khi truyền object vào method nhận reference tới interface cụ thể. Cách tiếp cận này giảm sự phụ thuộc, code cực kỳ linh hoạt: bạn có thể thêm triển khai mới mà không cần sửa code cũ.

5. Triển khai nhiều interface và method trùng tên ở các interface khác nhau

Đây mới là phần thú vị! Nếu hai interface đều yêu cầu method cùng tên nhưng ý nghĩa khác nhau thì sao? Ví dụ, interface cho máy pha cà phê:


public interface IStartable
{
    void Start();
}

public interface IRunnable
{
    void Start();
}

Máy pha cà phê có thể vừa là "có thể khởi động" (IStartable — bắt đầu pha chế), vừa là "runnable" (IRunnable — bắt đầu hoạt động nói chung).

Triển khai mặc định (bình thường):


public class CoffeeMachine : IStartable, IRunnable
{
    public void Start()
    {
        Console.WriteLine("Máy pha cà phê khởi động cho cả hai vai trò!");
    }
}

Trường hợp này một triển khai Start() "bao" cả hai interface.

Nếu cần ý nghĩa khác nhau thì sao?

Có thể dùng triển khai interface tường minh:


public class CoffeeMachine : IStartable, IRunnable
{
    void IStartable.Start()
    {
        Console.WriteLine("Khởi động: bắt đầu pha đồ uống!");
    }

    void IRunnable.Start()
    {
        Console.WriteLine("Khởi động: máy chuyển sang chế độ làm việc.");
    }
}

Lúc này chỉ có thể gọi triển khai cụ thể thông qua biến interface:


CoffeeMachine cm = new CoffeeMachine();

IStartable startable = cm;
startable.Start(); // Khởi động: bắt đầu pha đồ uống!

IRunnable runnable = cm;
runnable.Start(); // Khởi động: máy chuyển sang chế độ làm việc.

// cm.Start(); // Không biên dịch được! Start không truy cập được như method của class.

À, kiểu này hay dùng trong thư viện chuẩn .NET — ví dụ, khi class triển khai nhiều interface giống nhau từ các framework khác nhau, mỗi cái lại cần hành vi riêng.

6. Khác biệt giữa triển khai nhiều interface và kế thừa

Khả năng Class (kế thừa) Interface
Số lượng kiểu cha Chỉ một Muốn bao nhiêu cũng được
Kế thừa code Có (có thể có triển khai sẵn) Không, chỉ có chữ ký (trừ method mặc định)
Lưu trạng thái Không
Thêm vai trò mới Không (hoặc khó, phải dùng composition) Dễ, chỉ cần triển khai interface
Phù hợp cho… Hệ thống phân cấp "vật lý" "Vai trò"/khả năng logic

7. Ứng dụng thực tế: object "lai"

Nhờ triển khai nhiều interface, bạn có thể tạo class với bộ "vai trò" độc đáo, không cần nghĩ tới kế thừa lung tung.

Ví dụ, trong app ngân hàng — có thể tạo class vừa lưu thông tin về bản thân, vừa tự kiểm tra hợp lệ, vừa in ra — tất cả qua các interface khác nhau:


public interface IValidatable
{
    bool Validate();
}

public class Check : IPrintable, ISavable, IValidatable
{
    public string Data { get; set; }

    public void Print()
    {
        Console.WriteLine("In hóa đơn: " + Data);
    }

    public void Save(string filePath)
    {
        File.WriteAllText(filePath, Data);
        Console.WriteLine("Hóa đơn đã lưu: " + filePath);
    }

    public bool Validate()
    {
        return !string.IsNullOrEmpty(Data);
    }
}

Cách này giúp xây dựng kiến trúc dễ mở rộng, class kết hợp vai trò tùy ý. Nếu cần thêm "nhiệm vụ" mới — chỉ cần triển khai interface mới.

8. Lỗi phổ biến và bẫy thường gặp

Lỗi newbie hay gặp: quên triển khai đủ các thành phần của interface. Lúc này compiler không tha đâu — báo lỗi ngay, chỉ ra thiếu cái gì. Nhận ra lỗi này dễ: "Class phải triển khai thành phần của interface".

Vấn đề phổ biến thứ hai — nhầm lẫn về phạm vi truy cập method. Nếu bạn triển khai method tường minh, nó không truy cập được qua biến class — chỉ qua interface thôi.

Một tình huống nữa — refactor: bạn sửa interface (ví dụ, thêm method), nhưng không cập nhật hết các class triển khai. Dẫn tới lỗi biên dịch. Vì vậy khi thiết kế, cố gắng đừng thay đổi interface sau khi nhiều class đã dùng nó rồi.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Triển khai một class đơn giản cho nhiều interface
Triển khai một class đơn giản cho nhiều interface
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Sử dụng tham chiếu interface
Sử dụng tham chiếu interface
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION