مقدمه
تاکنون با وراثت از طریق کلاسهای پایه و انتزاعی به عنوان روشی برای ایجاد سلسله مراتب و استفاده
مجدد از کد آشنا شدیم. اکنون به یکی دیگر از قدرتمندترین ابزارهای برنامهنویسی شیءگرا در
C# میرسیم: اینترفیسها (Interfaces). یک اینترفیس، یک
قرارداد (contract) یا یک مشخصات فنی است. اینترفیس مجموعهای از اعضای عمومی
(متدها، پراپرتیها، رویدادها) را تعریف میکند، اما هیچگونه پیادهسازی برای آنها ارائه نمیدهد.
هر کلاس یا ساختاری که تصمیم بگیرد این اینترفیس را "پیادهسازی" کند، متعهد میشود که برای تمام
اعضای تعریفشده در آن قرارداد، یک پیادهسازی مشخص فراهم کند. اینترفیسها ابزار اصلی برای دستیابی
به اتصال سست (loose coupling) و چندریختی در سناریوهای پیچیده هستند.
اینترفیس (Interface) چیست؟
یک اینترفیس به این سؤال پاسخ میدهد که یک شیء "چه کارهایی میتواند انجام دهد؟" نه اینکه "چه چیزی
است؟". وراثت یک رابطهی "Is-A" (یک نوع از) را مدلسازی میکند، در حالی که اینترفیس یک رابطهی
"Can-Do" (قابلیت انجام کاری را دارد) را تعریف میکند. برای مثال، کلاسهای
Bird، Plane و Superman ممکن است هیچ وجه اشتراکی در سلسله مراتب وراثت خود نداشته باشند، اما
همهی آنها قابلیت پرواز کردن را دارند. بنابراین، میتوانیم یک اینترفیس به نام IFlyable تعریف
کنیم که همهی این کلاسها آن را پیادهسازی کنند.
طبق یک قرارداد نوشتاری قوی در .NET، نام تمام اینترفیسها با حرف بزرگ
I شروع میشود.
تعریف و پیادهسازی یک اینترفیس
برای تعریف یک اینترفیس از کلمهی کلیدی interface استفاده میکنیم.
اعضای تعریفشده در یک اینترفیس به طور پیشفرض public هستند و نیازی به نوشتن سطح دسترسی برای
آنها نیست.
Program.cs
public interface ILogger
{
void Log(string message);
}
اکنون هر کلاسی که بخواهد قابلیت لاگ کردن را داشته باشد، میتواند این اینترفیس را پیادهسازی کند.
برای پیادهسازی، از همان سینتکس دو نقطه که برای وراثت استفاده میشد، بهره میبریم.
Program.cs
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"CONSOLE LOG: {message}");
}
}
public class FileLogger : ILogger
{
public void Log(string message)
{
System.IO.File.AppendAllText("log.txt", message + "\n");
Console.WriteLine("Message logged to file.");
}
}
در این مثال، دو کلاس کاملاً متفاوت، ConsoleLogger و FileLogger، هر دو قرارداد ILogger را
پذیرفته و متد Log را به شیوهی خود پیادهسازی کردهاند.
استفاده از اینترفیسها برای چندریختی
زیبایی و قدرت اصلی اینترفیسها در قابلیت آنها برای ایجاد چندریختی است. شما میتوانید متغیری از
نوع یک اینترفیس تعریف کنید. این متغیر میتواند به هر شیئی از هر کلاسی که آن اینترفیس را
پیادهسازی کرده باشد، ارجاع دهد.
Program.cs
void ProcessMessage(string text, ILogger logger)
{
logger.Log(text);
}
ILogger consoleLogger = new ConsoleLogger();
ILogger fileLogger = new FileLogger();
ProcessMessage("First message", consoleLogger);
ProcessMessage("Second message", fileLogger);
متد ProcessMessage هیچ اطلاعی از نحوهی پیادهسازی Log ندارد. او فقط میداند که شیء logger
این قابلیت را دارد. این یعنی ما توانستهایم منطق پردازش پیام را از نحوهی لاگ کردن آن کاملاً جدا
کنیم. اگر در آینده یک DatabaseLogger هم اضافه کنیم، متد ProcessMessage بدون هیچ تغییری با آن
نیز کار خواهد کرد. این مفهوم اتصال سست (Loose Coupling) نام دارد و یکی از
مهمترین اصول در طراحی نرمافزارهای پایدار و قابل توسعه است.
پیادهسازی چندین اینترفیس
یکی از محدودیتهای وراثت در C# این است که یک کلاس فقط میتواند از یک کلاس پایه
ارثبری کند. اما یک کلاس میتواند هر تعداد اینترفیس که بخواهد را پیادهسازی
کند. این ویژگی به یک کلاس اجازه میدهد تا چندین "قرارداد" یا "قابلیت" متفاوت را ارائه دهد.
Program.cs
public interface ISerializable
{
string Serialize();
}
public class Report : ILogger, ISerializable
{
public string Title { get; set; }
public void Log(string message)
{
Console.WriteLine($"REPORT LOG: {message}");
}
public string Serialize()
{
return $"<report>{Title}</report>";
}
}
کلاس Report هم قابلیت لاگ شدن را دارد و هم قابلیت سریالایز شدن. این انعطافپذیری با وراثت از
کلاسها ممکن نیست.
اینترفیس یا کلاس انتزاعی؟
انتخاب بین اینترفیس و کلاس انتزاعی یک تصمیم مهم در طراحی است.
- از کلاس انتزاعی استفاده کنید زمانی که: میخواهید یک پیادهسازی مشترک و
پیشفرض برای برخی از اعضا فراهم کنید و کلاسها یک رابطهی "Is-A" قوی با هم دارند. (مثلاً
Dog و Cat هر دو Animal هستند).
- از اینترفیس استفاده کنید زمانی که: میخواهید یک "قابلیت" یا "توانایی"
را تعریف کنید که میتواند توسط کلاسهای کاملاً نامرتبط پیادهسازی شود (رابطهی
"Can-Do"). یا زمانی که یک کلاس نیاز به ارائهی چندین قرارداد متفاوت دارد.