رابط (Interface)

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

  1: open interface ISample
  2: {
  3:     open fun ShowMessage(message: String)
  4: }
  5: 
  6: class Sample : ISample
  7: {
  8:     override fun ShowMessage(message: String)
  9:     {
 10:         println(message)
 11:     }
 12: }
 13: 
 14: fun main(args: Array<String>)
 15: {
 16:     val sample = Sample()
 17: 
 18:     sample.ShowMessage("Implemented the ISample Interface!")
 19: }
Implemented the ISample Interface!

در خطوط 4-1 یک اینترفیس به نام ISample تعریف کرده‌ایم. بر طبق قراردادهای نامگذاری، اینترفیس ها به شیوه پاسکال نامگذاری می‌شوند و همه آنها باید با حرف I شروع شوند. همچنین در تعریف آنها باید از کلمه کلیدی interface استفاده شود. یک تابع در داخل بدنه اینترفیس تعریف می‌کنیم (خط 3). به این نکته توجه کنید که تابع تعریف شده فاقد بدنه است.

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

  1: class CA(var FullName: String, var Age: Int)
  2: 
  3: class CB(var FirstName: String, var LastName: String, personage: Int)
  4: {
  5:     var PersonsAge: Double = 0.toDouble()
  6: 
  7:     init
  8:     {
  9:         this.PersonsAge = personage.toDouble()
 10:     }
 11: }
 12: 
 13: fun printInfo(item: CA)
 14: {
 15:     println("Name: ${item.FullName}, Age ${item.Age}")
 16: }
 17: 
 18: fun main(args: Array<String>)
 19: {
 20:     val a = CA("John Doe", 35)
 21: 
 22:     printInfo(a)
 23: }
Name: John Doe, Age 35

در کد بالا دو کلاس CA و CB تعریف شده‌اند، در کلاس CA دو فیلد به نام FullName و Age و در کلاس CB سه فیلد به نام‌های FirstName و LastName ،PersonsAge تعریف کرده‌ایم. در برنامه، یک تابع به نام ()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: interface IInfo
  2: {
  3:     fun getName(): String
  4:     fun getAge() : String
  5: }
  6: 
  7: class CA(var FullName: String, var Age: Int) : IInfo
  8: {
  9:     override fun getName(): String
 10:     {
 11:         return FullName
 12:     }
 13: 
 14:     override fun getAge(): String
 15:     {
 16:         return Age.toString()
 17:     }
 18: }
 19: 
 20: class CB(var FirstName: String, var LastName: String, var PersonsAge: Int) : IInfo
 21: {
 22:     override fun getName(): String
 23:     {
 24:         return "$FirstName $LastName"
 25:     }
 26: 
 27:     override fun getAge(): String
 28:     {
 29:         return PersonsAge.toString()
 30:     }
 31: }
 32: 
 33: fun printInfo(item: IInfo)
 34: {
 35:     println("Name: ${item.getName()}, Age ${item.getAge()}")
 36: }
 37: 
 38: fun main(args: Array<String>)
 39: {
 40:     val a = CA("John Doe", 35)
 41:     val b = CB("Jane", "Doe", 33)
 42: 
 43:     printInfo(a)
 44:     printInfo(b)
 45: }
Name: John Doe, Age 35
Name: Jane Doe, Age 33

کد بالا را می‌توان به اینصورت توضیح داد که در خط 5-1 یک رابط به نام IInfo تعریف و آن را در خطوط 7 و 20 توسط دو کلاس CA و CB پیاده سازی کرده‌ایم. چون این دو کلاس وظیفه دارند توابع این رابط را پیاده سازی کنند، پس در خطوط 17-9 و 30-22 کدهای بدنه دو تابع این رابط را آن طور که می‌خواهیم، می‌نویسیم. در خط 33 تابع ()printInfo را طوری دستکاری می‌کنیم که یک پارامتر از نوع رابط دریافت کند. حال زمانی که دو شیء از دو کلاس CA و CB در دو خط 40 و 41 ایجاد می‌کنیم و آنها را در دو خط 43 و 44 به تابع ()PrintInfo ارسال می‌کنیم، چونکه این دو کلاس رابط IInfo را پیاده سازی کرده‌اند به طور صریح به رابط تبدیل می‌شود. یعنی کلاسی که یک رابط را پیاده سازی کند به طور صریح می‌تواند به رابط تبدیل شود. حال بسته به اینکه شیء کدام کلاس به تابع ()printInfo ارسال شده است، تابع مربوط به آن کلاس فراخوانی شده و مقادیر فیلدها چاپ می‌شود. می‌توان چند اینترفیس را در کلاس اجرا کرد.

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

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

  1: interface IFirstinterface
  2: {
  3:     fun FirstMessage(message: String)
  4: }
  5: 
  6: interface ISecondinterface
  7: {
  8:     fun SecondMessage(message: String)
  9: }
 10: 
 11: class Sample : IFirstinterface, ISecondinterface
 12: {
 13:     override fun FirstMessage(message: String)
 14:     {
 15:         println(message)
 16:     }
 17: 
 18:     override fun SecondMessage(message: String)
 19:     {
 20:         println(message)
 21:     }
 22: }
 23: 
 24: fun main(args: Array<String>)
 25: {
 26:     val sample=Sample()
 27: 
 28:     sample.FirstMessage("Implemented the IFirstinterface Interface!")
 29:     sample.SecondMessage("Implemented the ISecondinterface Interface!")
 30: }
Implemented the IFirstinterface Interface!
Implemented the ISecondinterface Interface!

درست است که می‌توان از چند اینترفیس در کلاس استفاده کرد ولی باید مطمئن شد که کلاس می‌تواند همه اعضای اینترفیسها را تغذیه کند. همانطور که در کد بالا مشاهده می‌کنید دو اینترفیس به نام‌های IFirstinterface و ISecondinterface در خطوط 9-1 تعریف شده‌اند که به ترتیب دارای دو تابع به نام‌های ()FirstMessage و ()SecondMessage می‌باشند. در خط 11 کلاس Sample این دو اینترفیس را پیاده سازی کرده است و درنتیجه همانطور که اشاره شد لازم است که کدهای بدنه دو تابع موجود در این دو رابط را تغذیه کند. که این کار در خطوط 21-13 انجام شده است. اگر یک کلاس از کلاس پایه ارث ببرد و در عین حال از اینترفیس ها هم استفاده کند، در این صورت باید نام کلاس پایه، همراه با پرانتز، ذکر شود. به شکل زیر :

class Sample : BaseClass(), ISample1, ISample2
{
}

اینترفیسها حتی می‌توانند از اینترفیسهای دیگر با استفاده از علامت دو نقطه ( : ) ارث بری کنند. به مثال زیر توجه کنید :

  1: interface IBase
  2: {
  3:     fun baseMethod()
  4: }
  5: 
  6: interface ISample : IBase
  7: {
  8:     fun showMessage(message: String)
  9: }
 10: 
 11: class Sample : ISample
 12: {
 13:     override fun showMessage(message: String)
 14:     {
 15:         println(message)
 16:     }
 17: 
 18:     override fun baseMethod()
 19:     {
 20:         println("Method from base interface!")
 21:     }
 22: }
 23: 
 24: fun main(args: Array<String>)
 25: {
 26:     val sample = Sample()
 27: 
 28:     sample.showMessage("Implemented the ISample Interface!")
 29:     sample.baseMethod()
 30: }
Implemented the ISample Interface!
Method from base interface!

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