الفرق بين المراجعتين ل"Kotlin/delegated properties"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(تعديل نتائج الشيفرات إلى text)
(تعديل مصطلح متحول)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:الخاصّيّات المُعمَّمة (Delegated Properties) في لغة Kotlin}}</noinclude>
+
== استخدام الخاصّيّات المُعمَّمة==
== استخدام الخاصّيّات المُعمَّمة ==
 
 
تستطيع في لغة Kotlin تعريف استخدام (implement) الخاصّيّات يدويًا مرارًا وتكرارًا بكل مرةٍ تحتاجها، ولكن من الأسهل تعريف استخدامها مرةً واحدةً وتخزين هذا التعريف في المكتبة (library) للاستفادة منه كلما دعت الحاجة، وهذا يشمل:
 
تستطيع في لغة Kotlin تعريف استخدام (implement) الخاصّيّات يدويًا مرارًا وتكرارًا بكل مرةٍ تحتاجها، ولكن من الأسهل تعريف استخدامها مرةً واحدةً وتخزين هذا التعريف في المكتبة (library) للاستفادة منه كلما دعت الحاجة، وهذا يشمل:
* الخاصّيّات الكسولة (Lazy property): تُحسب قيمتها مرةً واحدةً فقط وذلك عند الوصول إليها للمرّة الأولى.
+
*الخاصّيّات الكسولة (Lazy property): تُحسب قيمتها مرةً واحدةً فقط وذلك عند الوصول إليها للمرّة الأولى.
* الخاصّيّات المُراقَبة (observable property): إذ يُستدعَى مسؤول الانتظار (listener) عند حدوث أي تغييرٍ في الخاصّيّة.
+
*الخاصّيّات المُراقَبة (observable property): إذ يُستدعَى مسؤول الانتظار (listener) عند حدوث أي تغييرٍ في الخاصّيّة.
* تخزين الخاصّيّات في map بدلًا من حقلٍ منفصلٍ لكلِّ منها.
+
*تخزين الخاصّيّات في map بدلًا من حقلٍ منفصلٍ لكلِّ منها.
 
وتشمل لغة Kotlin كلّ تلك الحالات بدعمها للخاصّيّات المُعمَّمة (delegated properties) بالصيغة العامّة الآتية:<syntaxhighlight lang="kotlin">
 
وتشمل لغة Kotlin كلّ تلك الحالات بدعمها للخاصّيّات المُعمَّمة (delegated properties) بالصيغة العامّة الآتية:<syntaxhighlight lang="kotlin">
 
val/var <property name>: <Type> by <expression>
 
val/var <property name>: <Type> by <expression>
سطر 11: سطر 10:
 
     var p: String by Delegate()
 
     var p: String by Delegate()
 
}
 
}
</syntaxhighlight>إذ إن التعبير الواقع بعد الكلمة المفتاحيّة <code>by</code> سيكون هو المُعمَّم (delegate) لأن دالتيّ <code>get()‎</code> و <code>set()</code>‎ ستُعمَّمان للدالتين <code>getValue()</code>‎ و <code>setValue()‎</code> الموافقتين لهما، وبالتالي لا حاجة لتعريف استخدام (implement) أيّ واجهةٍ، بل يكفي وجود الدالة <code>getValue()‎</code> (والدالة <code>setValue()</code>‎ في حالة المتحولات من النوع <code>var</code>)، مثل الشيفرة الآتية:<syntaxhighlight lang="kotlin">
+
</syntaxhighlight>إذ إن التعبير الواقع بعد الكلمة المفتاحيّة <code>by</code> سيكون هو المُعمَّم (delegate) لأن دالتيّ <code>get()‎</code> و <code>set()</code>‎ ستُعمَّمان للدالتين <code>getValue()</code>‎ و <code>setValue()‎</code> الموافقتين لهما، وبالتالي لا حاجة لتعريف استخدام (implement) أيّ واجهةٍ، بل يكفي وجود الدالة <code>getValue()‎</code> (والدالة <code>setValue()</code>‎ في حالة المتغيِّرات من النوع <code>var</code>)، مثل الشيفرة الآتية:<syntaxhighlight lang="kotlin">
 
class Delegate {
 
class Delegate {
 
     operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
 
     operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
سطر 21: سطر 20:
 
     }
 
     }
 
}
 
}
</syntaxhighlight>فعند قراءة القيمة من <code>p</code> والمُعمَّمة لأي كائن (instance) من <code>Delegate</code> تُستدعَى الدالة <code>getValue()‎</code> من <code>Delegate</code> حيث يكون المتحول (parameter) الأوّل فيها هو الكائن الذي تٌقرَأ منه قيمة <code>p</code> ويحتوي الثاني على وصفٍ لها (للحصول على اسمها مثلًا)، مثل:<syntaxhighlight lang="kotlin">
+
</syntaxhighlight>فعند قراءة القيمة من <code>p</code> والمُعمَّمة لأي كائن (instance) من <code>Delegate</code> تُستدعَى الدالة <code>getValue()‎</code> من <code>Delegate</code> حيث يكون المعامل (parameter) الأوّل فيها هو الكائن الذي تٌقرَأ منه قيمة <code>p</code> ويحتوي الثاني على وصفٍ لها (للحصول على اسمها مثلًا)، مثل:<syntaxhighlight lang="kotlin">
 
val e = Example()
 
val e = Example()
 
println(e.p)
 
println(e.p)
 
</syntaxhighlight>وهذا سيُظهر النتيجة:<syntaxhighlight lang="text">
 
</syntaxhighlight>وهذا سيُظهر النتيجة:<syntaxhighlight lang="text">
 
Example@33a17727, thank you for delegating ‘p’ to me!
 
Example@33a17727, thank you for delegating ‘p’ to me!
</syntaxhighlight>وبشكلٍ مشابهٍ؛ تُستدعَى الدالة <code>setValue()‎</code> عند الإسناد للمتحول <code>p</code>، حيث يتماثل المتحولان الأول والثاني بينما يحتوي الثالث على القيمة المُسندَة، فعند تنفيذ الشيفرة الآتية:<syntaxhighlight lang="kotlin">
+
</syntaxhighlight>وبشكلٍ مشابهٍ؛ تُستدعَى الدالة <code>setValue()‎</code> عند الإسناد للمتغيِّر <code>p</code>، حيث يتماثل المعاملان الأول والثاني بينما يحتوي الثالث على القيمة المُسندَة، فعند تنفيذ الشيفرة الآتية:<syntaxhighlight lang="kotlin">
 
e.p = "NEW"
 
e.p = "NEW"
 
</syntaxhighlight>ستظهر النتيجة:<syntaxhighlight lang="text">
 
</syntaxhighlight>ستظهر النتيجة:<syntaxhighlight lang="text">
سطر 33: سطر 32:
  
 
'''ملاحظة:''' أصبح بالإمكان بدءًا من الإصدار Kotlin 1.1 التصريحُ عن الخاصّيَة المُعمَّمة داخل الدالة أو جزءٍ من الشيفرة (code block) ولا يُشترَط أن تكون عنصرًا (member) في الصنف، وستجد مثالًا عنها تحت عنوان الخاصيات المُعمَّمة المحليّة في هذه الصفحة.
 
'''ملاحظة:''' أصبح بالإمكان بدءًا من الإصدار Kotlin 1.1 التصريحُ عن الخاصّيَة المُعمَّمة داخل الدالة أو جزءٍ من الشيفرة (code block) ولا يُشترَط أن تكون عنصرًا (member) في الصنف، وستجد مثالًا عنها تحت عنوان الخاصيات المُعمَّمة المحليّة في هذه الصفحة.
 
+
==التعميمات القياسيّة (Standard Delegates)==
== التعميمات القياسيّة (Standard Delegates) ==
 
 
تحتوي مكتبة Kotlin القياسيّة على عددٍ من التوابع المُنتِجة (factory methods) لأنواع التعميمات المختلفة، وهي:
 
تحتوي مكتبة Kotlin القياسيّة على عددٍ من التوابع المُنتِجة (factory methods) لأنواع التعميمات المختلفة، وهي:
 
+
===الكسولة (Lazy)===
=== الكسولة (Lazy) ===
 
 
تأخذ الدالة <code>lazy()‎</code> تعبير lambda وتعيد كائنًا (instance) من النوع <code>Lazy<T></code>‎ المُعمَّم لتعريف استخدام الخاصية الكسولة، إذ تُحسَب قيمة تعبير lambda المُمرَّر للدالة عند الاستدعاء الأول (أي عند استخدام <code>get()</code>‎) لتُستخدَم نفسُ القيمة لكافّة الاستدعاءات اللاحقة، مثل:<syntaxhighlight lang="kotlin">
 
تأخذ الدالة <code>lazy()‎</code> تعبير lambda وتعيد كائنًا (instance) من النوع <code>Lazy<T></code>‎ المُعمَّم لتعريف استخدام الخاصية الكسولة، إذ تُحسَب قيمة تعبير lambda المُمرَّر للدالة عند الاستدعاء الأول (أي عند استخدام <code>get()</code>‎) لتُستخدَم نفسُ القيمة لكافّة الاستدعاءات اللاحقة، مثل:<syntaxhighlight lang="kotlin">
 
val lazyValue: String by lazy {
 
val lazyValue: String by lazy {
سطر 54: سطر 51:
  
  
</syntaxhighlight>وتُعدُّ عملية حساب الخاصّيّة الكسولة متزامنةً (synchronized) حيث تُحسَب في thread واحدٍ وستحصل كل threads الأخرى نفس القيمة، وإن لم يكن مهمًا أن تكون عملية التهيئة متزامنةً سيُسمح القيام بها بأكثر من thread بنفس الوقت وحينئذٍ يجب تمرير <code>LazyThreadSafetyMode.PUBLICATION</code> للمتحوّل الأول في الدالة <code>lazy()‎</code> إذ يُستخدَم النمط <code>LazyThreadSafetyMode.NONE</code> عندما يُضمَن أنّ عملية التهيئة لن تحدث بأكثر من thread.
+
</syntaxhighlight>وتُعدُّ عملية حساب الخاصّيّة الكسولة متزامنةً (synchronized) حيث تُحسَب في thread واحدٍ وستحصل كل threads الأخرى نفس القيمة، وإن لم يكن مهمًا أن تكون عملية التهيئة متزامنةً سيُسمح القيام بها بأكثر من thread بنفس الوقت وحينئذٍ يجب تمرير <code>LazyThreadSafetyMode.PUBLICATION</code> للمعامل الأول في الدالة <code>lazy()‎</code> إذ يُستخدَم النمط <code>LazyThreadSafetyMode.NONE</code> عندما يُضمَن أنّ عملية التهيئة لن تحدث بأكثر من thread.
 
+
===المُراقَبة (Observable)===
=== المُراقَبة (Observable) ===
+
تحتوي الدالة <code>Delegates.observable()‎</code> على وسيطين (arguments) وهما: القيمة الأوليّة (initial value) ومسؤول (handler) التعديلات، إذ يُستدعَى الوسيط الثاني بكلّ مرّةٍ يجري فيها إسنادٌ للخاصّيّة (بعد عمليّة الإسناد لا قبلها)، وتحتوي الدالة في بُنيتها (body) على ثلاثة معاملاتٍ: الخاصّيّة التي تُسنَد القيمة إليها والقيمة السابقة والقيمة الجديدة، كما في الشيفرة الآتية:<syntaxhighlight lang="kotlin">
تحتوي الدالة <code>Delegates.observable()‎</code> على متحولين (arguments) وهما: القيمة الأوليّة (initial value) ومسؤول (handler) التعديلات، إذ يُستدعَى المتحول الثاني بكلّ مرّةٍ يجري فيها إسنادٌ للخاصّيّة (بعد عمليّة الإسناد لا قبلها)، وتحتوي الدالة في بُنيتها (body) على ثلاثة متحولاتٍ: الخاصّيّة التي تُسنَد القيمة إليها والقيمة السابقة والقيمة الجديدة، كما في الشيفرة الآتية:<syntaxhighlight lang="kotlin">
 
 
import kotlin.properties.Delegates
 
import kotlin.properties.Delegates
  
سطر 87: سطر 83:
 
     "age"  to 25
 
     "age"  to 25
 
))
 
))
</syntaxhighlight>إذ تحصل الخاصّيّات المُعمَّمة على القيم من map عبر المفاتيح (key) والتي هي أسماء المتحوِّلات كما في الشيفرة:<syntaxhighlight lang="kotlin">
+
</syntaxhighlight>إذ تحصل الخاصّيّات المُعمَّمة على القيم من map عبر المفاتيح (key) والتي هي أسماء الخاصيِّات كما في الشيفرة:<syntaxhighlight lang="kotlin">
 
println(user.name) // ستظهر العبارة "John Doe"
 
println(user.name) // ستظهر العبارة "John Doe"
 
println(user.age)  // ستظهر القيمة 25
 
println(user.age)  // ستظهر القيمة 25
</syntaxhighlight>وهذا يصلُح أيضًا للخاصّيّات بالمتحوّلات من النوع <code>var</code> عبر استخدام <code>MutableMap</code> بدلًا من <code>Map</code> المُعدَّة للقراءة فقط (read-only)، لتصبح الشيفرة بالشكل الآتي:<syntaxhighlight lang="kotlin">
+
</syntaxhighlight>وهذا يصلُح أيضًا للخاصّيّات من النوع <code>var</code> عبر استخدام <code>MutableMap</code> بدلًا من <code>Map</code> المُعدَّة للقراءة فقط (read-only)، لتصبح الشيفرة بالشكل الآتي:<syntaxhighlight lang="kotlin">
 
class MutableUser(val map: MutableMap<String, Any?>) {
 
class MutableUser(val map: MutableMap<String, Any?>) {
 
     var name: String by map
 
     var name: String by map
سطر 97: سطر 93:
 
</syntaxhighlight>
 
</syntaxhighlight>
 
==الخاصيات المُعمَّمة المحليّة (بدءًا من الإصدار 1.1)==
 
==الخاصيات المُعمَّمة المحليّة (بدءًا من الإصدار 1.1)==
تسمح لغة Kotlin بالتصريح عن المتحولات المحليّة (local) كخاصيّات مُعمَّمة؛ كجعل المتحول المحليّ من النوع الكسول (lazy)، مثل:<syntaxhighlight lang="kotlin">
+
تسمح لغة Kotlin بالتصريح عن المتغيِّرات المحليّة (local variables) كخاصيّات مُعمَّمة؛ كجعل المتغير المحليّ من النوع الكسول (lazy)، مثل:<syntaxhighlight lang="kotlin">
 
fun example(computeFoo: () -> Foo) {
 
fun example(computeFoo: () -> Foo) {
 
     val memoizedFoo by lazy(computeFoo)
 
     val memoizedFoo by lazy(computeFoo)
سطر 105: سطر 101:
 
     }
 
     }
 
}
 
}
</syntaxhighlight>عندها ستُحسَب قيمة المتحول <code>memoizedFoo</code>  عند الوصول إليه للمرة الأولى فقط، وإذا حدث أيّ خللٍ  في الشرط <code>someCondition</code> فلن تُحسَب مطلقًا.
+
</syntaxhighlight>عندها ستُحسَب قيمة المتغيِّر <code>memoizedFoo</code>  عند الوصول إليه للمرة الأولى فقط، وإذا حدث أيّ خللٍ  في الشرط <code>someCondition</code> فلن تُحسَب مطلقًا.
 
+
==متطلبات تعميم الخاصيات (Property Delegate Requirements)==
==متطلبات تعميم الخاصّيّات (Property Delegate Requirements)==
+
*إن كانت الخاصّيّة مُعدَّة للقراءة فقط (read-only) أي أنّها من النوع <code>val</code> فيجب أن يحتوي التعميم منها على الدالة <code>getValue()‎</code> ولها المعاملات:
* إن كانت الخاصّيّة مُعدَّة للقراءة فقط (read-only) أي أنّها من النوع <code>val</code> فيجب أن يحتوي التعميم منها على الدالة <code>getValue()‎</code> ولها المتحولات:
+
**<code>thisRef</code> : وله نفس نوع الخاصّيّة الأصليّة أو أيّ نوعٍ أعلى (supertype)، وفي حالة الخاصّيّات الإضافيّة (extension properties) النوع المُوسَّع (extended).
** <code>thisRef</code> : وله نفس نوع الخاصّيّة الأصليّة أو أيّ نوعٍ أعلى (supertype)، وفي حالة الخاصّيّات الإضافيّة (extension properties) النوع المُوسَّع (extended).
+
**<code>property</code> : وله النوع <code>KProperty<*>‎</code> أو نوعه الأعلى.
** <code>property</code> : وله النوع <code>KProperty<*>‎</code> أو نوعه الأعلى.
 
 
وبإمكان هذه الدالة أن تعيد نفس النوع المُستخدَمِ في الخاصّيّة (أو أنواعها الفرعيّة [subtypes]).
 
وبإمكان هذه الدالة أن تعيد نفس النوع المُستخدَمِ في الخاصّيّة (أو أنواعها الفرعيّة [subtypes]).
* أمَا إن كانت الخاصّيّة متغيّرة (mutable) أي أنّها من النوع <code>var</code> فيجب أن تحتوي أيضًا على الدالة <code>setValue()‎</code> ولها المتحولات:
+
*أمَا إن كانت الخاصّيّة متغيّرة (mutable) أي أنّها من النوع <code>var</code> فيجب أن تحتوي أيضًا على الدالة <code>setValue()‎</code> ولها المعاملات:
** <code>thisRef</code>: كما في الدالة <code>getValue()‎</code>
+
**<code>thisRef</code>: كما في الدالة <code>getValue()‎</code>
** <code>property</code>: كما في الدالة<code>getValue()‎</code>
+
**<code>property</code>: كما في الدالة<code>getValue()‎</code>
** القيمة الجديدة: ويجب أن تكون من نفس نوع الخاصّيّة أو أيّ نوع أعلى (supertype).
+
**القيمة الجديدة: ويجب أن تكون من نفس نوع الخاصّيّة أو أيّ نوع أعلى (supertype).
 
وتكون الدالتان السابقتان (<code>getValue()‎</code> و <code>setValue()‎</code>) إمّا كعناصر في صنف التعميم (delegate class) أو كدوال إضافيّة (extension functions) حيث تُستخدَم الحالة الثانية عند تعميم خاصّيّة لكائنٍ (object) لا يحتوي بالأصل على هذه الدوال، إذ يجب استخدام الكلمة المفتاحيّة <code>operator</code> قبل أيّ منهما.
 
وتكون الدالتان السابقتان (<code>getValue()‎</code> و <code>setValue()‎</code>) إمّا كعناصر في صنف التعميم (delegate class) أو كدوال إضافيّة (extension functions) حيث تُستخدَم الحالة الثانية عند تعميم خاصّيّة لكائنٍ (object) لا يحتوي بالأصل على هذه الدوال، إذ يجب استخدام الكلمة المفتاحيّة <code>operator</code> قبل أيّ منهما.
  
سطر 141: سطر 136:
 
         set(value: Type) = prop$delegate.setValue(this, this::prop, value)
 
         set(value: Type) = prop$delegate.setValue(this, this::prop, value)
 
}
 
}
</syntaxhighlight>إذ يزوِّد المترجِم في لغة Kotlin بكافّة المعلومات اللازمة عن الخاصّيّة <code>prop</code> من خلال المتحولات: <code>this</code> والذي يشير إلى كائن (instance) الصنف الخارجيّ <code>C</code> و <code>this::prop</code> والذي هو كائن انعكاسيّ (reflection object) من النوع <code>KProperty</code>  الذي يصِف الخاصية <code>prop</code> نفسها.
+
</syntaxhighlight>إذ يزوِّد المترجِم في لغة Kotlin بكافّة المعلومات اللازمة عن الخاصّيّة <code>prop</code> من خلال الوسائط (arguments) وهي: <code>this</code> والذي يشير إلى كائن (instance) الصنف الخارجيّ <code>C</code> و <code>this::prop</code> والذي هو كائن انعكاسيّ (reflection object) من النوع <code>KProperty</code>  الذي يصِف الخاصية <code>prop</code> نفسها.
  
 
'''ملاحظة:''' إنّ الصيغة <code>this::prop</code> (والتي تُستخدَم للإشارة إلى [[Kotlin/reflection|المرجعيّة المرتبطة القابلة للاستدعاء [ bound callable reference]]] في الشيفرة مباشرةً) مُتاحةٌ بدءًا من الإصدار Kotlin 1.1.
 
'''ملاحظة:''' إنّ الصيغة <code>this::prop</code> (والتي تُستخدَم للإشارة إلى [[Kotlin/reflection|المرجعيّة المرتبطة القابلة للاستدعاء [ bound callable reference]]] في الشيفرة مباشرةً) مُتاحةٌ بدءًا من الإصدار Kotlin 1.1.
 
+
===<span> </span>التزويد بالتعميم (delegate) (بدءًا من الإصدار 1.1) ===
=== <span> </span>التزويد بالتعميم (delegate) (بدءًا من الإصدار 1.1) ===
 
 
من الممكن توسعة (extend) منطق إنشاء الكائنات الذي يُعمَّمُ له تعريف استخدام (implementation) الخاصّيّات عبر تعريف <code>provideDelegate</code> ، فإذا احتوى الكائن (المُستخدَم على الجانب الأيمن من <code>by</code>) على الدالة <code>provideDelegate</code>كدالةٍ في الصنف أو دالةٍ إضافيةٍ (extension)، فستُستدعَى هذه الدالة لإنشاء كائن تعميم الخاصّيّة (the property delegate instance)، إذ تفيد هذه الدالة في حالة التحقُّق من توافق الخاصّيّة (property consistency) عند إنشائها وليس فقط عند استخدامها (سواءً عبر getter أو setter).
 
من الممكن توسعة (extend) منطق إنشاء الكائنات الذي يُعمَّمُ له تعريف استخدام (implementation) الخاصّيّات عبر تعريف <code>provideDelegate</code> ، فإذا احتوى الكائن (المُستخدَم على الجانب الأيمن من <code>by</code>) على الدالة <code>provideDelegate</code>كدالةٍ في الصنف أو دالةٍ إضافيةٍ (extension)، فستُستدعَى هذه الدالة لإنشاء كائن تعميم الخاصّيّة (the property delegate instance)، إذ تفيد هذه الدالة في حالة التحقُّق من توافق الخاصّيّة (property consistency) عند إنشائها وليس فقط عند استخدامها (سواءً عبر getter أو setter).
  
سطر 172: سطر 166:
 
     val text by bindResource(ResourceID.text_id)
 
     val text by bindResource(ResourceID.text_id)
 
}
 
}
</syntaxhighlight>وتحتوي الدالة <code>provideDelegate()‎</code> (كما هو الحال في دالة <code>getValue()</code>‎) على المتحولات:
+
</syntaxhighlight>وتحتوي الدالة <code>provideDelegate()‎</code> (كما هو الحال في دالة <code>getValue()</code>‎) على المعاملات:
* <code>thisRef</code> : وله نفس نوع الخاصّيّة الأصليّة أو أيّ نوعٍ أعلى (supertype)، وفي حالة الخاصّيّات الإضافيّة (extension properties) النوع المُوسَّع (extended).
+
*<code>thisRef</code> : وله نفس نوع الخاصّيّة الأصليّة أو أيّ نوعٍ أعلى (supertype)، وفي حالة الخاصّيّات الإضافيّة (extension properties) النوع المُوسَّع (extended).
* <code>property</code> : وله النوع <code>KProperty<*>‎</code> أو نوعه الأعلى.
+
*<code>property</code> : وله النوع <code>KProperty<*>‎</code> أو نوعه الأعلى.
 
وتُستدعى الدالة لكل خاصّيّة عند عملية إنشاء الكائن (instance) من <code>MyUI</code> وتُنفِّذ مباشرةً عملياتِ التحقُّق اللازمة.
 
وتُستدعى الدالة لكل خاصّيّة عند عملية إنشاء الكائن (instance) من <code>MyUI</code> وتُنفِّذ مباشرةً عملياتِ التحقُّق اللازمة.
  
سطر 193: سطر 187:
 
   // إنشاء التعميم
 
   // إنشاء التعميم
 
}
 
}
</syntaxhighlight>إذ يُستدعَى التابع <code>provideDelegate</code> في الشيفرة المُولَّدة لتهيئة الخاصّيّة المساعدة <code>prop$delegate</code>.  
+
</syntaxhighlight>إذ يُستدعَى التابع <code>provideDelegate</code> في الشيفرة المُولَّدة لتهيئة الخاصّيّة المساعدة <code>prop$delegate</code>.
  
 
قارن بين الشيفرة الآتية المُولَّدة لتصريح الخاصّيّة: <code>val prop: Type by MyDelegate()‎</code> والشيفرة المُولَّدة سابقًا (والمشروحة في فقرة قواعد الترجمة حيث لا وجود للتابع <code>provideDelegate</code>):<syntaxhighlight lang="kotlin">
 
قارن بين الشيفرة الآتية المُولَّدة لتصريح الخاصّيّة: <code>val prop: Type by MyDelegate()‎</code> والشيفرة المُولَّدة سابقًا (والمشروحة في فقرة قواعد الترجمة حيث لا وجود للتابع <code>provideDelegate</code>):<syntaxhighlight lang="kotlin">
سطر 209: سطر 203:
 
}
 
}
 
</syntaxhighlight>حيث تؤثّر هذه الدالة على إنشاء الخاصية المساعدة (auxiliary property) فقط ولا تؤثِّر على الشيفرة المُولَّدة لأيِّ من getter أو setter.
 
</syntaxhighlight>حيث تؤثّر هذه الدالة على إنشاء الخاصية المساعدة (auxiliary property) فقط ولا تؤثِّر على الشيفرة المُولَّدة لأيِّ من getter أو setter.
 
+
==مصادر<span> </span>==
== مصادر ==
+
*[https://kotlinlang.org/docs/reference/delegated-properties.html صفحة الخاصّيّات المُعمَّمة في التوثيق الرسمي للغة Kotlin]<noinclude>{{DISPLAYTITLE:الخاصّيّات المُعمَّمة (Delegated Properties) في لغة Kotlin}}</noinclude>
* [https://kotlinlang.org/docs/reference/delegated-properties.html صفحة الخاصّيّات المُعمَّمة في التوثيق الرسمي للغة Kotlin]
 
 
[[تصنيف:Kotlin]]
 
[[تصنيف:Kotlin]]
 
[[تصنيف:Kotlin Delegations]]
 
[[تصنيف:Kotlin Delegations]]
 
[[تصنيف:Kotlin Properties]]
 
[[تصنيف:Kotlin Properties]]
 
[[تصنيف:Kotlin Maps]]
 
[[تصنيف:Kotlin Maps]]

مراجعة 16:41، 4 يوليو 2018

 استخدام الخاصّيّات المُعمَّمة

تستطيع في لغة Kotlin تعريف استخدام (implement) الخاصّيّات يدويًا مرارًا وتكرارًا بكل مرةٍ تحتاجها، ولكن من الأسهل تعريف استخدامها مرةً واحدةً وتخزين هذا التعريف في المكتبة (library) للاستفادة منه كلما دعت الحاجة، وهذا يشمل:

  • الخاصّيّات الكسولة (Lazy property): تُحسب قيمتها مرةً واحدةً فقط وذلك عند الوصول إليها للمرّة الأولى.
  • الخاصّيّات المُراقَبة (observable property): إذ يُستدعَى مسؤول الانتظار (listener) عند حدوث أي تغييرٍ في الخاصّيّة.
  • تخزين الخاصّيّات في map بدلًا من حقلٍ منفصلٍ لكلِّ منها.

وتشمل لغة Kotlin كلّ تلك الحالات بدعمها للخاصّيّات المُعمَّمة (delegated properties) بالصيغة العامّة الآتية:

val/var <property name>: <Type> by <expression>

كما في الشيفرة الآتية:

class Example {
    var p: String by Delegate()
}

إذ إن التعبير الواقع بعد الكلمة المفتاحيّة by سيكون هو المُعمَّم (delegate) لأن دالتيّ get()‎ و set()‎ ستُعمَّمان للدالتين getValue()‎ و setValue()‎ الموافقتين لهما، وبالتالي لا حاجة لتعريف استخدام (implement) أيّ واجهةٍ، بل يكفي وجود الدالة getValue()‎ (والدالة setValue()‎ في حالة المتغيِّرات من النوع var)، مثل الشيفرة الآتية:

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

فعند قراءة القيمة من p والمُعمَّمة لأي كائن (instance) من Delegate تُستدعَى الدالة getValue()‎ من Delegate حيث يكون المعامل (parameter) الأوّل فيها هو الكائن الذي تٌقرَأ منه قيمة p ويحتوي الثاني على وصفٍ لها (للحصول على اسمها مثلًا)، مثل:

val e = Example()
println(e.p)

وهذا سيُظهر النتيجة:

Example@33a17727, thank you for delegating ‘p’ to me!

وبشكلٍ مشابهٍ؛ تُستدعَى الدالة setValue()‎ عند الإسناد للمتغيِّر p، حيث يتماثل المعاملان الأول والثاني بينما يحتوي الثالث على القيمة المُسندَة، فعند تنفيذ الشيفرة الآتية:

e.p = "NEW"

ستظهر النتيجة:

NEW has been assigned to ‘p’ in Example@33a17727.

وسيأتي شرح متطلَّبات القيام بمثل هذا التعميم في فقرةٍ لاحقةٍ في الصفحة الحاليّة.

ملاحظة: أصبح بالإمكان بدءًا من الإصدار Kotlin 1.1 التصريحُ عن الخاصّيَة المُعمَّمة داخل الدالة أو جزءٍ من الشيفرة (code block) ولا يُشترَط أن تكون عنصرًا (member) في الصنف، وستجد مثالًا عنها تحت عنوان الخاصيات المُعمَّمة المحليّة في هذه الصفحة.

التعميمات القياسيّة (Standard Delegates)

تحتوي مكتبة Kotlin القياسيّة على عددٍ من التوابع المُنتِجة (factory methods) لأنواع التعميمات المختلفة، وهي:

الكسولة (Lazy)

تأخذ الدالة lazy()‎ تعبير lambda وتعيد كائنًا (instance) من النوع Lazy<T>‎ المُعمَّم لتعريف استخدام الخاصية الكسولة، إذ تُحسَب قيمة تعبير lambda المُمرَّر للدالة عند الاستدعاء الأول (أي عند استخدام get()‎) لتُستخدَم نفسُ القيمة لكافّة الاستدعاءات اللاحقة، مثل:

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}

سينتُج عن تنفيذ الشيفرة السابقة النتيجة:

computed!
Hello
Hello

وتُعدُّ عملية حساب الخاصّيّة الكسولة متزامنةً (synchronized) حيث تُحسَب في thread واحدٍ وستحصل كل threads الأخرى نفس القيمة، وإن لم يكن مهمًا أن تكون عملية التهيئة متزامنةً سيُسمح القيام بها بأكثر من thread بنفس الوقت وحينئذٍ يجب تمرير LazyThreadSafetyMode.PUBLICATION للمعامل الأول في الدالة lazy()‎ إذ يُستخدَم النمط LazyThreadSafetyMode.NONE عندما يُضمَن أنّ عملية التهيئة لن تحدث بأكثر من thread.

المُراقَبة (Observable)

تحتوي الدالة Delegates.observable()‎ على وسيطين (arguments) وهما: القيمة الأوليّة (initial value) ومسؤول (handler) التعديلات، إذ يُستدعَى الوسيط الثاني بكلّ مرّةٍ يجري فيها إسنادٌ للخاصّيّة (بعد عمليّة الإسناد لا قبلها)، وتحتوي الدالة في بُنيتها (body) على ثلاثة معاملاتٍ: الخاصّيّة التي تُسنَد القيمة إليها والقيمة السابقة والقيمة الجديدة، كما في الشيفرة الآتية:

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
}

والتي سينتُج عنها:

<no name> -> first
first -> second

ويُستفادُ في بعض الأحيان من الدالة ()vetoable بدلًا من observable()‎ ، حيث سيُستدعَى المسؤول (handler) المُمرَّر للدالة ()vetoable قبل تنفيذ عملية الإسناد للخاصّيّة.

تخزين الخاصّيّات في Map

يُستفاد من هذه الميّزة في التطبيقات الديناميكيّة أو في تحليل JSON‏ ( parsing JSON)، وفي هذه الحالة يُستخدَم الكائن map نفسه كنوعٍ مُعمَّمٍ للخاصّيّة، مثل:

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

ففي الشيفرة السابقة يحتوي الباني (constructor) على map بالشكل الآتي:

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

إذ تحصل الخاصّيّات المُعمَّمة على القيم من map عبر المفاتيح (key) والتي هي أسماء الخاصيِّات كما في الشيفرة:

println(user.name) // ستظهر العبارة "John Doe"
println(user.age)  // ستظهر القيمة 25

وهذا يصلُح أيضًا للخاصّيّات من النوع var عبر استخدام MutableMap بدلًا من Map المُعدَّة للقراءة فقط (read-only)، لتصبح الشيفرة بالشكل الآتي:

class MutableUser(val map: MutableMap<String, Any?>) {
    var name: String by map
    var age: Int     by map
}

الخاصيات المُعمَّمة المحليّة (بدءًا من الإصدار 1.1)

تسمح لغة Kotlin بالتصريح عن المتغيِّرات المحليّة (local variables) كخاصيّات مُعمَّمة؛ كجعل المتغير المحليّ من النوع الكسول (lazy)، مثل:

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

عندها ستُحسَب قيمة المتغيِّر memoizedFoo  عند الوصول إليه للمرة الأولى فقط، وإذا حدث أيّ خللٍ في الشرط someCondition فلن تُحسَب مطلقًا.

متطلبات تعميم الخاصيات (Property Delegate Requirements)

  • إن كانت الخاصّيّة مُعدَّة للقراءة فقط (read-only) أي أنّها من النوع val فيجب أن يحتوي التعميم منها على الدالة getValue()‎ ولها المعاملات:
    • thisRef : وله نفس نوع الخاصّيّة الأصليّة أو أيّ نوعٍ أعلى (supertype)، وفي حالة الخاصّيّات الإضافيّة (extension properties) النوع المُوسَّع (extended).
    • property : وله النوع KProperty<*>‎ أو نوعه الأعلى.

وبإمكان هذه الدالة أن تعيد نفس النوع المُستخدَمِ في الخاصّيّة (أو أنواعها الفرعيّة [subtypes]).

  • أمَا إن كانت الخاصّيّة متغيّرة (mutable) أي أنّها من النوع var فيجب أن تحتوي أيضًا على الدالة setValue()‎ ولها المعاملات:
    • thisRef: كما في الدالة getValue()‎
    • property: كما في الدالةgetValue()‎
    • القيمة الجديدة: ويجب أن تكون من نفس نوع الخاصّيّة أو أيّ نوع أعلى (supertype).

وتكون الدالتان السابقتان (getValue()‎ و setValue()‎) إمّا كعناصر في صنف التعميم (delegate class) أو كدوال إضافيّة (extension functions) حيث تُستخدَم الحالة الثانية عند تعميم خاصّيّة لكائنٍ (object) لا يحتوي بالأصل على هذه الدوال، إذ يجب استخدام الكلمة المفتاحيّة operator قبل أيّ منهما.

ومن الممكن أن يحتوي صنف التعميم على تعريف استخدام (implement) لأيّ من الواجهتين ReadOnlyProperty و ReadWriteProperty واللتان تحتويان على توابع operator المطلوبة، إذ إنّ الواجهتين مُعرَّفتان في المكتبة القياسيّة في Kotlin كما يلي:

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

قواعد الترجمة (Translation Rules)

يُولِّد المُترجِم (compiler) خاصّيّةً مساعدةً (auxiliary property) لكلّ خاصّيّة مُعمَّمة، فتُولَّد للخاصية prop مثلًا الخاصّيّة المخفيّة prop$delegate وتُعمَّم شيفرةُ الوصول لهذه الخاصّيّة الإضافيّة لتصبح بالشكل:

class C {
    var prop: Type by MyDelegate()
}

// تُولَّد هذه الشيفرة من قِبل المترجم
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

إذ يزوِّد المترجِم في لغة Kotlin بكافّة المعلومات اللازمة عن الخاصّيّة prop من خلال الوسائط (arguments) وهي: this والذي يشير إلى كائن (instance) الصنف الخارجيّ C و this::prop والذي هو كائن انعكاسيّ (reflection object) من النوع KProperty  الذي يصِف الخاصية prop نفسها.

ملاحظة: إنّ الصيغة this::prop (والتي تُستخدَم للإشارة إلى المرجعيّة المرتبطة القابلة للاستدعاء [ bound callable reference] في الشيفرة مباشرةً) مُتاحةٌ بدءًا من الإصدار Kotlin 1.1.

التزويد بالتعميم (delegate) (بدءًا من الإصدار 1.1) 

من الممكن توسعة (extend) منطق إنشاء الكائنات الذي يُعمَّمُ له تعريف استخدام (implementation) الخاصّيّات عبر تعريف provideDelegate ، فإذا احتوى الكائن (المُستخدَم على الجانب الأيمن من by) على الدالة provideDelegateكدالةٍ في الصنف أو دالةٍ إضافيةٍ (extension)، فستُستدعَى هذه الدالة لإنشاء كائن تعميم الخاصّيّة (the property delegate instance)، إذ تفيد هذه الدالة في حالة التحقُّق من توافق الخاصّيّة (property consistency) عند إنشائها وليس فقط عند استخدامها (سواءً عبر getter أو setter).

فللتحقُّق مثلًا من اسم الخاصية قبل تقييدها (binding)، تصبح الشيفرة:

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}
    
class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        //  إنشاء التعميم
        return ResourceDelegate()
    }

    private fun checkProperty(thisRef: MyUI, name: String) { ... }
}

class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }

    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

وتحتوي الدالة provideDelegate()‎ (كما هو الحال في دالة getValue()‎) على المعاملات:

  • thisRef : وله نفس نوع الخاصّيّة الأصليّة أو أيّ نوعٍ أعلى (supertype)، وفي حالة الخاصّيّات الإضافيّة (extension properties) النوع المُوسَّع (extended).
  • property : وله النوع KProperty<*>‎ أو نوعه الأعلى.

وتُستدعى الدالة لكل خاصّيّة عند عملية إنشاء الكائن (instance) من MyUI وتُنفِّذ مباشرةً عملياتِ التحقُّق اللازمة.

وبدون تلك المقدرة على تحقيق الصلة ما بين الخاصّيّة وتعميمها، يجب تمرير اسم الخاصّيّة بشكلٍ صريحٍ لإنجاز نفس المهمة السابقة، ويصعُب ذلك كما في الشيفرة الآتية:

// التحقق من اسم الخاصيات دون الاعتماد على
// provideDelegate الدالة

class MyUI {
    val image by bindResource(ResourceID.image_id, "image")
    val text by bindResource(ResourceID.text_id, "text")
}

fun <T> MyUI.bindResource(
        id: ResourceID<T>,
        propertyName: String
): ReadOnlyProperty<MyUI, T> {
   checkProperty(this, propertyName)
   // إنشاء التعميم
}

إذ يُستدعَى التابع provideDelegate في الشيفرة المُولَّدة لتهيئة الخاصّيّة المساعدة prop$delegate. قارن بين الشيفرة الآتية المُولَّدة لتصريح الخاصّيّة: val prop: Type by MyDelegate()‎ والشيفرة المُولَّدة سابقًا (والمشروحة في فقرة قواعد الترجمة حيث لا وجود للتابع provideDelegate):

class C {
    var prop: Type by MyDelegate()
}

// يولِّد المترجم هذه الشيفرة بوجود الدالة
// 'provideDelegate'
class C {
    // استدعاء الدالة لإنشاء تعميم الخاصية الإضافية
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)
}

حيث تؤثّر هذه الدالة على إنشاء الخاصية المساعدة (auxiliary property) فقط ولا تؤثِّر على الشيفرة المُولَّدة لأيِّ من getter أو setter.

مصادر