التصريح عن الكائنات (Object Declarations) وتعابيرها (Expressions) في لغة Kotlin

من موسوعة حسوب
اذهب إلى: تصفح، ابحث

قد تحتاج في بعض الأحيان لإنشاء كائنٍ بإجراء تعديلاتٍ طفيفةٍ على أحد الأصناف (classes) بدون التصريح عن صنفٍ فرعيٍّ (subclass) له؛ تعالج لغة Java مثل هذه الحالات بالاعتماد على الأصناف الداخليّة المجهولة (anonymous inner classes)، وتُعمِّمها لغة Kotlin من خلال طرح مفهوم التصريح عن الكائنات وتعابيرها.

تعابير الكائنات (Object Expressions)

لإنشاء كائنٍ من صنفٍ مجهولٍ (anonymous) يرِث من نوعٍ أو أكثر تكون الشيفرة بالشكل:
window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }

    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})
وإن كان للصنف الأعلى (superclass) بانٍ (constructor)، فيجب أن تُمرَّر له معاملاتٌ مناسبةٌ، وتُحدَّد كثير من الأنواع الأعلى (supertypes) كقائمةٍ (يٌفصَل بين عناصرها بالفاصلة ,) تُكتب بعد النقطتين الرأسيتيّن :، كما في الشيفرة الآتية:
open class A(x: Int) {
    public open val y: Int = x
}

interface B {...}

val ab: A = object : A(1), B {
    override val y = 15
}
أما عند الحاجة لكائنٍ وحسب بدون تعقيدات الأنواع الأعلى، تصبح الشيفرة ببساطةٍ كالآتي:
fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}
وقد تُستخدَم الكائنات المجهولة (anonymous objects) كأنواع في حالة التصريحات المحليّة (local) أو الخاصّة (private) فقط، وإذا ما استُخدم الكائن كنوعٍ مُعادٍ في دالةٍ عامّةٍ أو كنوع خاصيّةٍ (property) عامٍة فإن النوع الفعليّ لتلك الدالة أو الخاصّيّة سيكون بحسب النوع الأعلى للكائن المجهول، أو من النوع Any إذا لم يُعرَّف أيُّ نوعٍ أعلى، ولن يُتاح الوصول إلى العناصر المٌضافة في الكائن المجهول كما في الشيفرة الآتية:
class C {
    // الدالة خاصّة لذلك فإن النوع المعاد هو نوع الكائن المجهول
    private fun foo() = object {
        val x: String = "x"
    }

    // الدالة عامّة وبالتالي فإن النوع المُعاد هو Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // شيفرة صحيحة وتعمل
        val x2 = publicFoo().x  // شيفرة خاطئة إذ لا يمكن الوصول عبر المرجعية x
    }
}
وكما هو الحال في الأصناف الداخليّة المجهولة (anonymous inner classes) في Java فإن شيفرة تعابير الكائنات تستطيع الوصول لكافّة المتغيِّرات الواقعة في نفس النطاق المحيط (enclosing scope) (وهذا لا يقتصر على المتغيِّرات من النوع final كما في Java)، مثل الشيفرة الآتية:
fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ...
}

التصريح عن الكائنات (Object Declarations)

يُستفاد من نموذج Singelton التصميميّ في حالاٍت كثيرةٍ، وتجعل Kotlin (من بعد Scala) من التصريح عن Singelton أمرًا سهلًا كما في الشيفرة:
object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}
وهذا ما يُدعَى بالتصريح عن الكائن (object declaration)، ودائمًا يُتبع بالتسمية بعد الكلمة المفتاحيّة object، وكما في التصريح عن المتغيِّرات لا يُعدُّ هذا التصريح تعبيرًا، ولا يمكن استخدامه على الجانب الأيمن من تعليمة الإسناد، أمّا تهيئته الأولية (initialization) فهي خالية من المشاكل التفرعيّة (thread-safe) (أي لا تنتُج مشاكلٌ برمجيّةٌ في التهيئة التفرعيّة عبر threads مختلفة) ويٌشار للكائن باستخدام اسمه بشكلٍ مباشرٍ، مثل:
DataProviderManager.registerDataProvider(...)
وقد يكون لهذا النوع من الكائنات أنواعٌ أعلى (supertypes) مثل:
object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }

    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
}
ملاحظة: لا يُمكن أن يكون التصريح عن الكائنات محليًّا (local) (كأن يكون واقعًا في دالةٍ ما)، ولكن يمكن إدخالها (nested) في التصريحات عن كائناتٍ أخرى أو أيّ أصناف غير داخليّة (non-inner).

الكائنات المُرافِقة (Companion Objects)

تُضاف الكلمة المفتاحية companion قبل التصريح عن الكائن إن كان واقعًا داخل الصنف، مثل:
class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}
إذ تُستدعى عناصر الكائن المرافق من خلال اسم الصنف كمُقيِّد (qualifier)، مثل:
val instance = MyClass.create()
كما ويمكن الاستغناء عن اسم الكائن المرافق ليُستخدَم الاسم Companion عوضًا عنه، مثل:
class MyClass {
    companion object {
    }
}

val x = MyClass.Companion
وعلى الرغم من أن عناصر الكائن المرافق تماثِل العناصر الستاتيكيّة في لغات البرمجة الأخرى، ولكنها تبقى عناصر كائنيّة عاديّة أثناء التنفيذ (runtime)، وبالتالي فإنها تستطيع تعريف استخدام (implement) الواجهات (interfaces)، مثل:
interface Factory<T> {
    fun create(): T
}


class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}
ولكن من الممكن توليد عناصر الكائنات المرافقة في JVM كتوابع (methods) وحقول (fields) ستاتيكيّةٍ حقيقيّةٍ وذلك باستخدام التوصيف (annotation)‏ ‎@JvmStatic.

الفوارق الدِلاليّة بين التصريح عن الكائنات وتعابيرها

هناك فرقٌ أساسيّ واحدٌ ما بينهما يتلخص بالنقاط:

  • تُنفَّذ (وتُهيَّأ) تعابير الكائن في المكان الذي تُستخدَم به فوريًا
  • تكون التهيئة من النمط الكسول (lazy) في التصريح عن الكائنات عند الوصول إليها للمرّة الأولى
  • يُهيَّأ الكائن المرافق (companion object) عند تحميل (load) الصنف الموافق له، مماثلًا بذلك عمليات التهيئة الستاتيكيّة في Java.

مصادر