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.
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ênIPlayabletrong không gian tênMedia. 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ứcPlay()duy nhất, trả về một chuỗi.MediaFile.cs: Định nghĩa một lớp trừu tượng có tênMediaFiletrong không gian tênMedia. 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ínhTitle(chuỗi) và thuộc tínhDuration(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ênGetInfo(), trả về"{Title} ({Duration}s)". Đồng thời khai báo một phương thức trừu tượngGetType()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ớpAudioFiletrong không gian tênMedia, kế thừa từMediaFilevà triển khaiIPlayable. Thêm thuộc tínhArtist(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 khaiPlay()để trả về"Playing audio: {Title} by {Artist}".Program.cs: Trong tệp chính, tạo mộtAudioFilebằng các giá trị đầu vào. Minh họa cả hai khía cạnh của lớp: gọiGetInfo()vàGetType()(được kế thừa từ lớp trừu tượng), sau đó gọiPlay()(từ giao diện). Lưu tệp âm thanh vào một biến kiểuIPlayablerồi gọi lạiPlay()để 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 QueenHã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
}
}
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
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online