رابط ها (Interfaces)

رابط‌ها یا اینترفیس ها شبیه به کلاسها هستند اما فقط شامل تعاریفی برای متدها و خواص (Property) می‌باشند. رابط‌ها را می‌توان به عنوان پلاگین های کلاس‌ها در نظر گرفت. کلاسی که یک رابط خاص را پیاده سازی می‌کند لازم است که کدهایی برای اجرا توسط اعضا و متدهای آن فراهم کند چون اعضا و متدهای رابط هیچ کد اجرایی در بدنه خود ندارند. اجازه دهید که نحوه تعریف و استفاده از یک رابط در کلاس را توضیح دهیم :

   1: using System;                                                    
   2:                                                                  
   3: interface ISample                                                
   4: {                                                                
   5:     void ShowMessage(string message);                            
   6: }                                                                
   7:                                                                  
   8: public class Sample : ISample                                    
   9: {                                                                
  10:     public void ShowMessage(string message)                      
  11:     {                                                            
  12:         Console.WriteLine(message);                              
  13:     }                                                            
  14: }                                                                
  15:                                                                  
  16: class Program                                                    
  17: {                                                                
  18:     public static void Main()                                    
  19:     {                                                            
  20:         Sample sample = new Sample();                            
  21:                                                                  
  22:         sample.ShowMessage("Implemented the ISample Interface!");
  23:     }                                                            
  24: }
Implemented the ISample Interface!

در خطوط 6-3 یک رابط به نام ISample تعریف کرده‌ایم. بر طبق قراردادهای نامگذاری، رابط‌ها به شیوه پاسکال نامگذاری می‌شوند و همه آنها باید با حرف I شروع شوند. یک متد در داخل بدنه رابط تعریف می‌کنیم (خط 5). به این نکته توجه کنید که متد تعریف شده فاقد بدنه است و در آخران باید از سیمیکولن استفاده شود.
وقتی که متد را در داخل رابط تعریف می‌کنید فقط لازم است که عنوان متد (نوع، نام و پارامترهای آن) را بنویسید. به این نکته نیز توجه کنید که متدها و خواص تعریف شده در داخل رابط سطح دسترسی ندارند چون باید همیشه هنگام اجرای کلاسها در دسترس باشند. وقتی یک کلاس نیاز به اجرای یک رابط داشته باشد، از همان روشی که در وراثت استفاده می‌کردیم، استفاده می‌کنیم. کلاسی که رابط را اجرا می‌کند کدهای واقعی را برای اعضای آن فراهم می‌کند. همانطور که در مثال بالا می‌بینید کلاس Sample ، متد ShowMessage() رابط ISample را اجرا و تغذیه می‌کند.

برای روشن شدن کاربرد رابط‌ها به مثال زیر توجه کنید:

   1: using System;
   2: 
   3: class CA
   4: {
   5:     public string FullName;
   6:     public int Age;
   7: }
   8: 
   9: class CB
  10: {
  11:     public string FirstName;
  12:     public string LastName;
  13:     public double PersonsAge;
  14: }
  15: 
  16: class Program
  17: {
  18:     static void PrintInfo(CA item)
  19:     {
  20:         Console.WriteLine("Name: {0}, Age {1}", item.FullName, item.Age);
  21:     }
  22:     static void Main()
  23:     {
  24:         CA a = new CA() { FullName = "John Doe", Age = 35 };
  25: 
  26:         PrintInfo(a);
  27: 
  28:         Console.ReadLine();
  29:     }
  30: }

در کد بالا دو کلاس CA و CB تعریف شده‌اند، در کلاس CA دو فیلد به نام FullName و Age و در کلاس CB سه فیلد به نام‌های FirstName و LastName , PersonsAge تعریف کرده‌ایم. در کلاس Program یک متد به نام ()PrintInfo داریم که یک پارامتر از نوع کلاس CA دارد. به شکل ساده در این متد مقدار فیلدهای شی ای که به این متد ارسال شده است، چاپ می‌شود. در متد Main یک شیء از کلاس CA ساخته‌ایم و فیلدهای آن را مقدار دهی کرده‌ایم. سپس این شیء را به متد PrintInfo ارسال می‌کنیم. کلاس‌های CA و CB از نظر مفهومی شبیه یکدیگر هستند. مثلاً کلاس CA فیلد FullName را برای نمایش نام و نام خانوادگی دارد ولی کلاس CB برای نمایش نام و نام خانوادگی دو فیلد جدا از هم به نام‌های FirstName و LastName را دارد. و همچنین یک فیلد برای نگهداری مقدار سن داریم که در کلاس CA نام آن Age و در کلاس CB نام آن PersonAge می‌باشد. مشکل اینجاست که اگر ما یک شیء از کلاس CA را به متد (PrintInfo) ارسال کنیم از آنجایی که در داخل بدنه این متد فقط مقدار دو فیلد چاپ می‌شود اگر بخواهیم یک شیء از کلاس CB را به آن ارسال کنیم که دارای سه فیلد است با خطا مواجه می‌شویم (زیرا متد ()PrintInfo با ساختار کلاس CA سازگار است و فیلدهای CB را نمی‌شناسد). برای رفع این مشکل باید ساختار دو کلاس CA و CB را شبیه هم کنیم و این کار را با استفاده از Interface انجام می‌دهیم.

   1: using System;
   2: 
   3: interface IInfo      
   4: {                    
   5:     string GetName();
   6:     string GetAge(); 
   7: }                    
   8: 
   9: class CA : IInfo
  10: {
  11:     public string FullName;
  12:     public int Age;
  13:     public string GetName() { return FullName;       }
  14:     public string GetAge()  { return Age.ToString(); }
  15: }
  16: 
  17: class CB : IInfo
  18: {
  19:     public string FirstName;
  20:     public string LastName;
  21:     public double PersonsAge;
  22:     public string GetName() { return FirstName + " " + LastName; }
  23:     public string GetAge()  { return PersonsAge.ToString();      }
  24: }
  25: 
  26: class Program
  27: {
  28:     static void PrintInfo(IInfo item)
  29:     {
  30:         Console.WriteLine("Name: {0}, Age {1}", item.GetName(), item.GetAge());
  31:     }
  32: 
  33:     static void Main()
  34:     {
  35:         CA a = new CA() { FullName = "John Doe", Age = 35 };
  36:         CB b = new CB() { FirstName = "Jane", LastName = "Doe", PersonsAge = 33 };
  37: 
  38:         PrintInfo(a);
  39:         PrintInfo(b);
  40: 
  41:         Console.ReadLine();
  42:     }
  43: }

کد بالا را می‌توان به اینصورت توضیح داد که در خط 7-3 یک رابط به نام IInfo تعریف و آن را در خطوط 9 و 17 توسط دو کلاس CA و CB پیاده سازی کرده‌ایم. چون این دو کلاس وظیف دارند متدهای این رابط را پیاده سازی کنند پس در خطوط 14-13 و 23-22 کدهای بدنه دو متد این رابط را آن طور که می‌خواهیم، می‌نویسیم. در خط 28 متد ()PrintInfo را طوری دستکاری می‌کنیم که یک پارامتر از نوع رابط دریافت کند. حال زمانی که دو شیء از دو کلاس CA و CB در دو خط 35 و 36 ایجاد می‌کنیم و آنها را در دو خط 38 و 39 به متد ()PrintInfo ارسال می‌کنیم، چونکه این دو کلاس رابط IInfo را پیاده سازی کرده‌اند به طور صریح به رابط تبدیل می‌شود. یعنی کلاسی که یک رابط را پیاده سازی کند به طور صریح می‌تواند به رابط تبدیل شود. حال بسته به اینکه شیء کدام کلاس به متد ()PrintInfo ارسال شده است، متد مربوط به آن کلاس فراخوانی شده و مقادیر فیلدها چاپ می‌شود. می‌توان چند رابط را در کلاس اجرا کرد :

class Sample : ISample1, ISample2, ISample3
{
   //Implement all interfaces
}

درست است که می‌توان از چند رابط در کلاس استفاده کرد ولی باید مطمئن شد که کلاس می‌تواند همه اعضای رابطها را تغذیه کند. اگر یک کلاس از کلاس پایه ارث ببرد و در عین حال از رابط‌ها هم استفاده کند، در این صورت باید نام کلاس پایه قبل از نام رابط‌ها ذکر شود. به شکل زیر :

class Sample : BaseClass, ISample1, ISample2
{
}

همچنین می‌توان از عملگر is برای چک کردن اینکه آیا یک شیء خاص از یک رابط استفاده می‌کند یا نه استفاده کرد :

Sample sample = new Sample();

if(sample is ISample)
{
   Console.WriteLine("sample implements the ISample Interface!");
}

نکته دیگر اینکه نمی‌توان از یک رابط نمونه‌ای ایجاد کرد چون رابط‌ها دارای سازنده نیستند، مثلاً کد زیر اشتباه است :

ISample sample = new ISample();

کد زیر یک رابط که دارای یک property هست را نشان می‌دهد :

interface ISample
{
   int Number { get; set; }
}

نباید هیچ کدی در قسمت get و set خاصیت نوشته شود. کلاسی که رابط را پیاده سازی می‌کند آن را اجرا می‌کند.

class Sample : ISample
{
   private int number;

   public int Number
   {
      get { return number; }
      set { number = value; }
   }
}

رابط‌ها حتی می‌توانند رابطهای دیگر را پیاده سازی یا اجرا کنند. به مثال زیر توجه کنید :

   1: using System;
   2: 
   3: interface IBase
   4: {
   5:     void BaseMethod();
   6: }
   7: 
   8: interface ISample : IBase
   9: {
  10:     void ShowMessage(string message);
  11: }
  12: 
  13: public class Sample : ISample
  14: {
  15:     public void ShowMessage(string message)
  16:     {
  17:         Console.WriteLine(message);
  18:     }
  19: 
  20:     public void BaseMethod()
  21:     {
  22:         Console.WriteLine("Method from base interface!");
  23:     }
  24: }
  25: 
  26: class Program
  27: {
  28:     public static void Main()
  29:     {
  30:         Sample sample = new Sample();
  31: 
  32:         sample.ShowMessage("Implemented the ISample Interface!");
  33:         sample.BaseMethod();
  34:     }
  35: }

مشاهده می‌کنید که حتی اگر کلاس Sample فقط رابط ISample را پیاده سازی کند، لازم است که همه اعضای IBase را هم پیاده سازی کند چون ISample از آن ارث بری می‌کند (خط 8).