الـ Interface في Kotlin ومتى تستخدمونها
تسمح Kotlin بأن تحتوي الـ Interface على كود، أي أن الصنف (class) يستطيع تنفيذ Interface ويرث سلوكها.

بعد استخدام Kotlin في تطوير تطبيقات Android لفترة، أدركت مؤخرًا فائدة الـ Interface في Kotlin. ففي Java 6 لا يمكن استخدام الـ Interface إلا لوصف السلوكيات، دون تنفيذها.
ولحسن الحظ، تسمح Kotlin بأن تحتوي الـ Interface على كود، أي أن الصنف (class) يستطيع تنفيذ Interface ويرث سلوكها.
بدايةً، إذا كنتم قادمين من Java، فربما لديكم interface كهذه.
interface FlyingVehicle {
fun startEngine()
}
لنجعل صنفَي Aeroplane وHelicopter ينفّذان الـ Interface بهذا الشكل.
class AeroPlane : FlyingVehicle {
val engine: Engine = Engine()
override fun startEngine() = engine.start()
}
class Helicopter : FlyingVehicle {
val engine: Engine = Engine()
override fun startEngine() = engine.start()
}
لا عيب فيهما، صحيح؟ لكن بما أن Kotlin تسمح بوضع كود داخل الـ Interface، يمكننا تعريف property ليُعاد تعريفها (override) وتنفيذ الدوال داخل الـ Interface هكذا
interface FlyingVehicle {
val engine: Engine
fun startEngine() = engine.start()
}
ثم يكفي أن نعيد تعريف الـ property في أصنافنا كما يلي.
class AeroPlane : FlyingVehicle {
override val engine: Engine = Engine()
}
class Helicopter : FlyingVehicle {
override val engine: Engine = Engine()
}
وعندما نريد تشغيل محرك الطائرة أو المروحية، يمكننا استدعاء الدالة مباشرة هكذا!
val aeroPlane = AeroPlane()
val helicopter = Helicopter()
aeroPlane.startEngine()
helicopter.startEngine()
قد تتساءلون كيف نستخدم هذا عمليًا (بما يتجاوز تشغيل محرك طائرة ومروحية).
سآخذ مثالًا مستمدًا من جزء من كتاب Kotlin for Android Developer للمؤلف Antonio Leiva، وهو كتاب ممتاز لكل مطوّر Android يريد البدء باستخدام Kotlin.
باختصار، سننشئ interface تساعدنا على
- تعيين عنوان الـ toolbar
- التعامل مع النقر على قائمة الإعدادات
interface SettingToolbarManager {
val toolbar: Toolbar
var toolbarTitle: String
get() = toolbar.title.toString()
set(value) {
toolbar.title = value
}
fun setOnMenuClickListener(onSettingClicked: () -> Unit) {
toolbar.inflateMenu(R.menu.menu_setting)
toolbar.setOnMenuItemClickListener {
when(it.itemId) {
R.id.action_setting -> onSettingClicked()
else -> {}
}
}
}
}
ثم في MainActivity أو أي activity أخرى تحتاج إلى قائمة الإعدادات، يمكننا جعلها تنفّذ الـ interface.
class MainActivity : AppCompatActivity(), SettingToolbarManager {
override val toolbar by lazy { findViewById<Toolbar>(R.id.toolbar) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
toolbarTitle = "My Main Activity"
setOnMenuClickListener {
//Do what you want here
}
}
}
كما ترون، تساعدنا الـ interface على فصل بعض الكود المشترك عن الـ activities، فيبدو الكود أنظف قليلًا. كما يمكننا إعادة استخدامها في أماكن متعددة، فلا نحتاج إلى كتابة الكود نفسه مرارًا وتكرارًا. لكن الـ interface ليست دائمًا الخيار المناسب لتبسيط الكود. لننظر في المثال التالي.
لننشئ interface تبسّط عملية إنشاء Toast
interface ToastManager {
val context: Context
fun shortToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
fun longToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
}
قد تبدو الـ activity التي تنفّذ هذه الـ interface هكذا.
class ToastActivity : AppCompatActivity(), ToastManager {
override val context = this
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
shortToast("This is short toast")
}
}
يبدو كل شيء سليمًا في المثال أعلاه، فـ ToastManager تؤدي عملها جيدًا. لكن في هذه الحالة لدينا حل أسهل: يمكننا استخدام Kotlin Extension!!
فبدلًا من إنشاء interface كما فعلنا، يمكننا كتابة extension functions.
fun Activity.shortToast(text: String) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}
fun Activity.longToast(text: String) {
Toast.makeText(this, text, Toast.LENGTH_LONG).show()
}
وكما ترون في المثال أدناه، يمكننا الوصول إلى الدالة longToast دون تنفيذ interface أو إعادة تعريف context، لأن ToastActivity ترث من AppCompatActivity، وهي بدورها نسخة (instance) من Activity.
class ToastActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
longToast("This is long toast")
}
}
نقاش وخلاصة
Interface أم Extension
في رأيي، إذا كانت الوظيفة التي تريدون تحقيقها مشتركة بين كل نسخ صنف معيّن، فالـ Extension هي الخيار الأنسب. أما إذا أردتم سلوكًا أكثر تخصيصًا، فالـ Interface أنسب، مثل SettingToolbarManager في المثال أعلاه.
هذا مجرد رأيي الشخصي، فأخبروني إن كنتم لا توافقون عليه. :)
وفي كل الأحوال، الـ Interface طريقة رائعة لتبسيط الكود وتجنب التكرار.


