Menu
CoddyTech

Interface và lớp trừu tượng

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 26 trên 70.

Cả giao diện lẫn lớp trừu tượng đều cho phép bạn định nghĩa các quy ước mà những lớp khác phải tuân theo, nhưng chúng phục vụ những mục đích khác nhau và có các khả năng riêng.

Giao diện (interface) xác định những gì một lớp phải làm mà không có phần triển khai nào. Đây là một bản cam kết thuần túy:

public interface IFlyable
{
    void Fly();
}

public class Bird : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("Flapping wings");
    }
}

Một lớp trừu tượng có thể cung cấp cả hợp đồng lẫn cách triển khai dùng chung. Đây là một bản thiết kế chưa hoàn chỉnh:

public abstract class Vehicle
{
    public string Brand { get; set; }
    
    public void StartEngine()
    {
        Console.WriteLine("Engine started");
    }
    
    public abstract void Drive();  // Phải triển khai
}

public class Car : Vehicle
{
    public override void Drive()
    {
        Console.WriteLine("Driving on roads");
    }
}

Những điểm khác biệt chính nằm ở quy tắc kế thừa và mục đích.

Một lớp có thể triển khai nhiều giao diện nhưng chỉ kế thừa từ một lớp trừu tượng.

Một lớp cũng có thể vừa kế thừa từ một lớp trừu tượng, vừa triển khai các giao diện: sau dấu hai chấm, lớp cơ sở đứng trước, tiếp theo là các giao diện, cách nhau bằng dấu phẩy:

public class Airplane : Vehicle, IFlyable
{
    public override void Drive()
    {
        Console.WriteLine("Taxiing to the runway");
    }

    public void Fly()
    {
        Console.WriteLine("Flying above the clouds");
    }
}

Dùng interface khi các lớp không liên quan cần dùng chung hành vi, chẳng hạn như IFlyable cho chim và máy bay. Dùng lớp trừu tượng khi các lớp có liên quan dùng chung mã và trạng thái, chẳng hạn như các phương tiện dùng chung cơ chế khởi động động cơ.

Lớp trừu tượng có thể có hàm khởi tạo, trường và phạm vi truy cập trên các thành viên. Theo truyền thống, giao diện không thể có những thành phần này, dù các phiên bản C# hiện đại cho phép triển khai mặc định.

Khi phân vân, hãy ưu tiên giao diện để tăng tính linh hoạt. Bạn luôn có thể thêm lớp trừu tượng sau nếu cần dùng chung phần triển khai.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống trình phát đa phương tiện để minh họa khi nào nên dùng giao diện thay vì lớp trừu tượng. Bạn sẽ tạo một thiết kế trong đó các thiết bị không liên quan chia sẻ hành vi phát qua một giao diện, còn các loại phương tiện có liên quan chia sẻ phần triển khai chung qua một lớp trừu tượng.

Bạn sẽ sắp xếp code trong bốn tệp:

  • IPlayable.cs: Định nghĩa một giao diện có tên IPlayable trong không gian tên Media. Giao diện này quy định hợp đồng cho mọi thứ có thể phát nội dung đa phương tiện, dù đó là điện thoại, TV hay máy nghe nhạc chuyên dụng. Giao diện cần khai báo một phương thức Play() duy nhất, trả về một chuỗi.
  • MediaFile.cs: Định nghĩa một lớp trừu tượng có tên MediaFile trong không gian tên Media. Lớp này cung cấp nền tảng chung cho mọi tệp đa phương tiện. Lớp cần có thuộc tính Title (chuỗi) và thuộc tính Duration (số nguyên, biểu thị số giây). Thêm một hàm khởi tạo thiết lập cả hai giá trị. Thêm một phương thức cụ thể có tên GetInfo(), trả về "{Title} ({Duration}s)". Đồng thời khai báo một phương thức trừu tượng GetType() trả về một chuỗi, để mỗi loại phương tiện tự xác định đó là loại tệp nào.
  • AudioFile.cs: Định nghĩa lớp AudioFile trong không gian tên Media, kế thừa từ MediaFile và triển khai IPlayable. Thêm thuộc tính Artist (chuỗi). Hàm khởi tạo cần nhận tiêu đề, thời lượng và nghệ sĩ. Ghi đè GetType() để trả về "Audio". Triển khai Play() để trả về "Playing audio: {Title} by {Artist}".
  • Program.cs: Trong tệp chính, tạo một AudioFile bằng các giá trị đầu vào. Minh họa cả hai khía cạnh của lớp: gọi GetInfo() và GetType() (được kế thừa từ lớp trừu tượng), sau đó gọi Play() (từ giao diện). Lưu tệp âm thanh vào một biến kiểu IPlayable rồi gọi lại Play() để thể hiện tính đa hình qua giao diện.

Bạn sẽ nhận ba đầu vào:

  • Tên bài hát
  • Thời lượng tính bằng giây
  • Tên nghệ sĩ

In đầu ra theo định dạng này:

Info: {GetInfo() result}
Type: {GetType() result}
Direct: {Play() result}
Via Interface: {Play() result from IPlayable variable}

Ví dụ: nếu đầu vào là Bohemian Rhapsody, 354 và Queen, đầu ra sẽ là:

Info: Bohemian Rhapsody (354s)
Type: Audio
Direct: Playing audio: Bohemian Rhapsody by Queen
Via Interface: Playing audio: Bohemian Rhapsody by Queen

Hãy chú ý cách AudioFile tận dụng cả hai cách tiếp cận: lớp này kế thừa phần triển khai dùng chung (GetInfo()) từ lớp trừu tượng, đồng thời đáp ứng hợp đồng của IPlayable. Lớp trừu tượng giúp tái sử dụng code giữa các loại phương tiện có liên quan, còn giao diện cho phép mọi lớp không liên quan có thể phát nội dung mà không cần chung một hệ thống kế thừa.

Tự làm thử

using System;
using Media;

class Program
{
    public static void Main(string[] args)
    {
        // Đọc các giá trị đầu vào
        string title = Console.ReadLine();
        int duration = Convert.ToInt32(Console.ReadLine());
        string artist = Console.ReadLine();
        
        // TODO: Tạo AudioFile bằng các giá trị đầu vào
        
        // TODO: In ra Info: rồi đến kết quả của GetInfo()
        
        // TODO: In ra Type: rồi đến kết quả của GetType()
        
        // TODO: In ra Direct: rồi đến kết quả của Play()
        
        // TODO: Lưu tệp âm thanh vào một biến IPlayable
        
        // TODO: In ra Via Interface: rồi đến kết quả của Play() từ biến IPlayable
    }
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch C# online