English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
نموذج التوكيل هو تقنية أساسية في تصميم البرمجيات. في نموذج التوكيل، يشارك اثنان من العناصر في معالجة نفس الطلب، حيث يتم تفويض الطلب إلى عنصر آخر للمعالجة.
يدعم Kotlin نموذج التوكيل بشكل مباشر، مما يجعله أكثر روعة وسهولة. يتم التوكيل في Kotlin باستخدام كلمة المفتاح by.
توكيل الفئة تشير إلى أن طريقة معينة معرفة في فئة في الواقع تنفذ عن طريق استدعاء طريقة نموذج آخر.
في هذا المثال، تنحدر فئة المشتقة Derived من جميع طرق واجهة Base، وتفوض تنفيذ هذه الطرق لنموذج Base المقدم.
// إنشاء واجهة interface Base { fun print() {} // فئة معقولة تنفذ هذه الواجهة class BaseImpl(val x: Int) : Base { override fun print() { print(x) } {} // إنشاء فئة التوكيل باستخدام كلمة المفتاح by class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // يخرج 10 {}
في بيانات الارتباط Derived، جملة by تشير إلى أن b يتم حفظها داخل نموذج Derived، وسيقوم محرر البرمجة بإنشاء جميع الطرق التي تنحدر من واجهة Base، وسيتم توجيه الطلبات إلى b.
توكيل الميزة يشير إلى أن قيمة ميزة معينة لفئة ليست معرفة مباشرة داخل الفئة، بل يتم تفويضها لفئة وسيطة، مما يتيح إدارة متسقة للميزات في الفئة.
تنسيق جملة التوكيل للميزة:
val/var <اسم الميزة>: <نوع> بواسطة <تعبير>
var/val: نوع الميزة (قابل للتغيير/قابل للقراءة فقط)
اسم الميزة: اسم الميزة
النوع: نوع البيانات الخاص بالميزة
تعبير: فئة التوكيل
التعبير الذي يلي كلمة by هو التكليف، ويتم تكليف دالة get() (و دالة set() إذا كانت الخاصية var) بتلك الكائن getValue() و setValue() لهذا الكائن. لا يتطلب تكليف الخاصيات تنفيذ أي واجهة، ولكن يجب تقديم دالة getValue() (و دالة setValue() إذا كانت الخاصية var).
يجب أن يحتوي هذا الكلاس على دالة getValue() و setValue()، ويجب أن يكون معامل thisRef هو مثال الكلاس المكتلوف، ويجب أن يكون prop هو مثال الخاصية المكتلوفة.
import kotlin.reflect.KProperty // تعريف كلاس يحتوي على تكليف الخاصيات class Example { var p: String by Delegate() {} // كلاس التكليف class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef، هنا تم تكليف الخاصية ${property.name}" {} operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$thisRef الخاصية ${property.name} تم تعيينها إلى $value") {} {} fun main(args: Array<String>) { val e = Example() println(e.p) // زيارة الخاصية، استدعاء دالة getValue() e.p = "w3codebox" // استدعاء دالة setValue() println(e.p) {}
النتيجة الصادرة هي:
Example@433c675d، هنا تم تكليف الخاصية p مقدار الخاصية p في Example@433c675d هو w3codebox Example@433c675d، هنا تم تكليف الخاصية p
مكتبة Kotlin القياسية تحتوي بالفعل على العديد من طرق المصنع لتحقيق التكليف للخصائص.
lazy() هو دالة تأخذ تعبير Lambda كمعامل، وتعيد دالة مثال Lazy <T>، يمكن استخدام الصيغة المعدة كوكل للخصائص الم延缓ة: عند استدعاء get() لأول مرة، يتم تنفيذ التعبير المعطى لـ lazy() وتسجيل النتيجة، والاستدعاءات اللاحقة لـ get() تعود فقط بالنتيجة المسجلة.
val lazyValue: String by lazy {}} println("computed!") // النداء الأول مخرج، النداء الثاني لا يتم تنفيذه "Hello" {} fun main(args: Array<String>) { println(lazyValue) // التنفيذ الأول، التنفيذ مرتين مخرج التعبير println(lazyValue) // التنفيذ الثاني، فقط مخرج القيمة {}
نتائج التنفيذ:
computed! Hello Hello
يمكن استخدام observable لتحقيق نموذج المراقب (observer).
يأخذ دالة Delegates.observable معاملين: الأول هو القيمة الافتراضية، والثاني هو مستمع الحدث (handler) لتغيير قيمة الخاصية.
يتم تنفيذ مستمع الحدث (handler) بعد تخصيص الخصائص، ويأخذ ثلاثة معاملات: الخاصية المخصصة، القيمة القديمة والقيمة الجديدة:
import kotlin.properties.Delegates class User { var name: String by Delegates.observable("القيمة الافتراضية") { prop, old, new -> println("القيمة القديمة: $old -> القيمة الجديدة: $new") {} {} fun main(args: Array<String>) { val user = User() user.name = "المرة الأولى لتخصيص" user.name = "المرة الثانية لتخصيص" {}
نتائج التنفيذ:
القيمة القديمة: القيمة الافتراضية -> القيمة الجديدة: المرة الأولى لتخصيص القيمة القديمة: المرة الأولى لتخصيص -> القيمة الجديدة: المرة الثانية لتخصيص
استخدام حالة شائعة لتخزين قيم الخصائص في تحويل (map). هذا يحدث غالبًا في تطبيقات مثل تحليل JSON أو القيام بأشياء "ديناميكية" أخرى. في هذه الحالة، يمكنك استخدام نموذج تحويل (map) نفسه كوكل لتحقيق خصائص الوكل (proxy).
class Site(val map: Map<String, Any?>) { val name: String by map val url: String by map {} fun main(args: Array<String>) { // معادلة البناء تأخذ معامل تحويل val site = Site(mapOf( "name" إلى "موقع تعليمي أساسي", "url" to "ar.oldtoolbag.com" )) // قراءة قيمة التحويل println(site.name) println(site.url) {}
نتائج التنفيذ:
موقع تعليمي أساسي ar.oldtoolbag.com
إذا كنت تستخدم خاصية var، يجب استبدال Map بـ MutableMap:
class Site(val map: MutableMap<String, Any?>) {}} val name: String by map val url: String by map {} fun main(args: Array<String>) { var map: MutableMap<String, Any?> = mutableMapOf( "name" إلى "موقع تعليمي أساسي", "url" إلى "ar.oldtoolbag.com" ) val site = Site(map) println(site.name) println(site.url) println("--------------") map.put("name", "Google") map.put("url", "www.google.com") println(site.name) println(site.url) {}
نتائج التنفيذ:
موقع تعليمي أساسي ar.oldtoolbag.com -------------- Google www.google.com
NotNull ينطبق على الحالات التي لا يمكن فيها تحديد قيمة الخاصية في مرحلة التجميع.
class Foo { var notNullBar: String by Delegates.notNull<String>() {} foo.notNullBar = "bar" println(foo.notNullBar)
يجب الانتباه إلى أن إذا تم الوصول إلى الخاصية قبل الت assignation، فإنه سيتم إلقاء استثناء.
يمكنك إعلان المتغيرات المحلية كخصائص تكليف. على سبيل المثال، يمكنك جعل متغير محلي يتم تعيينه بطريقة تأخيرية:
fun example(computeFoo: () -> Foo) { val memoizedFoo by lazy(computeFoo) إذا (someCondition && memoizedFoo.isValid()) { memoizedFoo.doSomething() {} {}
المتغير memoizedFoo سيتم حسابه فقط عند أول زيارة. إذا فشل someCondition، فإن هذا المتغير لن يتم حسابه على الإطلاق.
للخصائص القابلة للقراءة (أي ما يُدعى val الخاصية)، يجب أن يقدم المندوب وظيفة تُدعى getValue(). يجب أن تأخذ هذه الوظيفة المعلمات التالية:
thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
property —— 必须是类型 KProperty 或其超类型
这个函数必须返回与属性相同的类型(或其子类型)。
对于一个值可变(mutable)属性(也就是说,var 属性),除 getValue()函数之外,它的委托还必须另外再提供一个名为setValue()的函数, 这个函数接受以下参数:
thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
property —— 必须是类型 KProperty 或其超类型
new value —— 必须和属性同类型或者是它的超类型。
在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。例如,对于属性 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 的所有必要信息:第一个参数 this 引用到外部类 C 的示例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。
通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来创建属性委托示例。
provideDelegate 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中)检查属性一致性。
例如,如果要在绑定之前检查属性名称,可以这样写:
class ResourceLoader<T>(id: ResourceID<T>) { operator fun provideDelegate( thisRef: MyUI, prop: KProperty<*> ): ReadOnlyProperty<MyUI, T> { checkProperty(thisRef, prop.name) // إنشاء المندوب {} private fun checkProperty(thisRef: MyUI, name: String) { …… } {} fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… } class MyUI { val image by bindResource(ResourceID.image_id) val text by bindResource(ResourceID.text_id) {}
معاملات provideDelegate تساوي مع getValue:
thisRef —— يجب أن يكون نفس نوع مالك الخاصية (للمخصيات الموسعة، يجب أن يكون نوع النوع الموسع) أو نوعه الأعلى
property —— يجب أن يكون نوع KProperty أو نوعه الأعلى.
في أثناء إنشاء مثال MyUI، استدعاء طريقة provideDelegate لكل خاصية، ثم تنفيذ التحقق الضروري على الفور.
إذا لم يكن لديك القدرة على تمرير ارتباط بين الخاصية الم拦截ة والمندوب، فإنك تحتاج لتقديم اسم الخاصية بشكل صريح لتحقيق نفس الوظيفة، وهذا ليس مفيدًا جدًا:
// تحقق من اسم الخاصية دون استخدام خاصية “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 { // استدعاء "provideDelegate" لإنشاء خصائص "delegate" الإضافية private val prop$delegate = MyDelegate().provideDelegate(this, this::prop) val prop: Type get() = prop$delegate.getValue(this, this::prop) {}
يرجى ملاحظة أن طريقة provideDelegate تؤثر فقط على إنشاء الخصائص المساعدة وليس على الكود المولد للgetter أو setter.