رابط (interface)

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

   1: #include <iostream>
   2: #include <string>
   3: using namespace std;
   4: 
   5: __interface ISample
   6: {
   7:     void ShowMessage(string message);
   8: };
   9: 
  10: class Sample : public ISample
  11: {
  12:     public:
  13:         void ShowMessage(string message)
  14:         {
  15:             cout << message << endl;
  16:         }
  17: };
  18: 
  19: int main()
  20: {
  21:     Sample sample;
  22: 
  23:     sample.ShowMessage("Implemented the ISample Interface!");
  24: }
Implemented the ISample Interface!

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

   1: #include <iostream>
   2: #include <string>
   3: using namespace std;
   4: 
   5: class CA
   6: {
   7:     public: 
   8:         string FullName;
   9:         int Age;
  10:         
  11:         CA(string fullname, int age)
  12:         {
  13:             this->FullName = fullname;
  14:             this->Age      = age;
  15:         }
  16: };
  17: 
  18: class CB
  19: {
  20:     public: 
  21:         string FirstName;
  22:         string LastName;
  23:         int PersonsAge;
  24: 
  25:         CB(string firstname, string lastname, int personage)
  26:         {
  27:             this->FirstName  = firstname;
  28:             this->LastName   = lastname;
  29:             this->PersonsAge = personage;
  30:         }
  31: };
  32: 
  33: static void PrintInfo(CA item)
  34: {
  35:     cout << "Name: " << item.FullName << " , " << "Age: " << item.Age;
  36: }
  37: 
  38: int main()
  39: {
  40:     CA a("John Doe", 35);
  41: 
  42:     PrintInfo(a);
  43: }
Name: John Doe , Age: 35

در کد بالا دو کلاس 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: #include <iostream>
   2: #include <string>
   3: using namespace std;
   4: 
   5: __interface IInfo
   6: {
   7:     string GetName();
   8:     string GetAge();
   9: };
  10: 
  11: class CA : public IInfo
  12: {
  13:     public: 
  14:         string FullName;
  15:         int    Age;                
  16: 
  17:         CA(string fullname, int age)
  18:         {
  19:             this->FullName = fullname;
  20:             this->Age      = age;
  21:         }
  22: 
  23:         string GetName() { return FullName; }
  24:         string GetAge()  { return to_string(Age); }
  25: };
  26: 
  27: class CB : public IInfo
  28: {
  29:     public: 
  30:         string FirstName;
  31:         string LastName;
  32:         int    PersonsAge;        
  33: 
  34:         CB(string firstname, string lastname, int personage)
  35:         {
  36:             this->FirstName  = firstname;
  37:             this->LastName   = lastname;
  38:             this->PersonsAge = personage;
  39:         }
  40: 
  41:         string GetName() { return FirstName + " " + LastName; }
  42:         string GetAge()  { return to_string(PersonsAge); }
  43: };
  44: 
  45: void PrintInfo(IInfo& item)
  46: {
  47:     cout << "Name: " << item.GetName() << " , " << "Age: " << item.GetAge() << endl; 
  48: }
  49: 
  50: int main()
  51: {
  52:     CA a("John Doe", 35);
  53:     CB b("Jane", "Doe", 33);
  54: 
  55:     PrintInfo(a);
  56:     PrintInfo(b);
  57: }
Name: John Doe , Age: 35
Name: Jane Doe , Age: 33

کد بالا را می توان به اینصورت توضیح داد که در خط 9-5 یک رابط به نام IInfo تعریف و آن را در خطوط 11 و 27 توسط دو کلاس CA و CB پیاده سازی کرده ایم. چون این دو کلاس وظیف دارند متدهای این رابط را پیاده سازی کنند پس در خطوط 24-23 و 42-41 کدهای بدنه دو متد این رابط را آن طور که می خواهیم، می نویسیم. در خط 45 متد ()PrintInfo را طوری دستکاری می کنیم که یک پارامتر از نوع رابط دریافت کند. حال زمانی که دو شیء از دو کلاس CA و CB در دو خط 52 و 53 ایجاد می کنیم و آنها را در دو خط 55 و 56 به متد ()PrintInfo ارسال می کنیم، چونکه این دو کلاس رابط IInfo را پیاده سازی کرده اند به طور صریح به رابط تبدیل می شود. یعنی کلاسی که یک رابط را پیاده سازی کند به طور صریح می تواند به رابط تبدیل شود. حال بسته به اینکه شیء کدام کلاس به متد ()PrintInfo ارسال شده است، متد مربوط به آن کلاس فراخوانی شده و مقادیر فیلدها چاپ می شود. می توان چند رابط را در کلاس اجرا کرد :

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

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

class Sample : public BaseClass, public ISample1, public ISample2
{

};

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

   1: #include <iostream>
   2: #include <string>
   3: using namespace std;
   4: 
   5: __interface IBase
   6: {
   7:     void BaseMethod();
   8: };
   9: 
  10: __interface ISample : IBase
  11: {
  12:     void ShowMessage(string message);
  13: };
  14: 
  15: class Sample : public ISample
  16: {
  17:     public: 
  18:         void ShowMessage(string message)
  19:         {
  20:             cout << message << endl;
  21:         }
  22: 
  23:         void BaseMethod()
  24:         {
  25:             cout << "Method from base interface!" << endl;
  26:         }
  27: };
  28: 
  29: int main()
  30: {
  31:     Sample sample;
  32: 
  33:     sample.ShowMessage("Implemented the ISample Interface!");
  34:     sample.BaseMethod();
  35: }
Implemented the ISample Interface!
Method from base interface!

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