Kotlin / السلوك القابل للاستخدام والحدود المنمطة / الدرس 11
تركيب السلوك بالواجهات والتفويض وأعمار الكائنات
حدّد عقود القدرات، وفوّض السلوك باستخدام by، واختر أعمار كائنات صريحة بدلا من حالة مشتركة خفية.
في هذه الصفحة
أهداف الدرس
- اشرح خصائص الواجهة وتعارضات التنفيذ والاستدعاءات المفوضة.
- ميّز تصريح object، وتعبير object، والكائن المرافق companion object.
استخدم واجهة لعقد سلوك متماسك
تستطيع الواجهة إعلان دوال وتوفير تنفيذ افتراضي لها، ووصف خصائص بتصريحات مجردة أو دوال وصول. لكنها لا تملك حقولا خلفية تخزّن خصائصها؛ وتوفّر الأصناف المنفذة الحالة الفعلية عند الحاجة. يمكن لواجهة LabelSource صغيرة فصل حاجة المستدعي إلى نص عن كيفية إنتاجه. تجنب واجهة شاملة تجمع عمليات تخزين وبريد وعرض لا تجمعها مسؤولية واحدة. يسهل استبدال القدرات الضيقة واختبارها، لكن دلالات الخطأ والحجب والملكية تظل بحاجة إلى توثيق يتجاوز مجرد تواقيع الطرق.
أظهر مسؤوليات التنفيذ والمستدعي
يقدّم المثال الكامل تنفيذا وواجهة تقبلها دالة مستقلة. لا يطلب المستدعي سوى label، ولا يبني المزود داخليا. يسهّل ذلك الاستبدال دون افتراض إطار عمل أو اكتشاف ديناميكي أثناء التشغيل. إذا قدّمت واجهات عدة التنفيذ الموروث نفسه، فقد يحتاج الصنف إلى تجاوز صريح يحل التعارض باستدعاءات super مؤهلة. لا يثبت اشتراك طريقتين في الاسم توافق المعنى؛ فافحص الوعود السلوكية قبل جمع واجهات غير مرتبطة في صنف واحد.
interface LabelSource { fun label(): String }
class FixedLabel(private val value: String) : LabelSource {
override fun label(): String = value
}
fun render(source: LabelSource): String = "[${source.label()}]"
fun main() {
println(render(FixedLabel("workshop")))
val local = object : LabelSource {
override fun label(): String = "local"
}
println(render(local))
}
[workshop]
[local]
المخرجان المتوقعان هما [workshop] ثم [local]. ينشئ تعبير object تنفيذا محليا لهذا السياق من الاستدعاء. لا يُستدعى مزود خارجي، ولا يُنفّذ مترجم Kotlin.
يفوّض by تنفيذ العقد إلى كائن آخر
يمكن لصنف ينفّذ واجهة بواسطة مفوض أن يترك للمترجم توليد أعضاء تمرر الاستدعاءات إليه. قد يتجاوز الغلاف أعضاء مختارة، لكن الاستدعاءات التي يجريها المفوض داخل نفسه تظل تعمل على تنفيذه وحالته هو. لا تفترض أن تجاوز خاصية في الغلاف يغير تلقائيا قراءة المفوض الداخلية. التفويض تركيب مع تمرير، وليس وراثة لهوية تنفيذ الكائن المفوض. يفيد لبناء غلاف مركّز، لكن على الغلاف تحديد ملكية الموارد وإغلاقها وترجمة الاستثناءات وأمان الخيوط؛ فهذه مسؤوليات لا ينشئها الاختصار by تلقائيا.
تجاوز عملية مع تفويض البقية
للواجهة في المثال عمليتان مستقلتان. يغير الغلاف label، بينما يفوّض code إلى الكائن الداخلي. يسهل توقع النتيجة لأن أيا من الطريقتين المفوضتين لا تستدعي داخليا طريقة واجهة أخرى. في غلاف حقيقي، اختبر التفاعلات داخل المفوض والحفاظ على سلوك الفشل. لا تستخدم التفويض لإخفاء معنى عمل مختلف خلف الواجهة نفسها. إذا ضيّق الغلاف المدخلات الصالحة أو غيّر كون العملية دائمة، فيحتاج المستدعي إلى تغيير عقد صريح، لا إلى حيلة تنفيذ لا يراها.
interface WorkshopInfo {
fun label(): String
fun code(): String
}
class BasicInfo : WorkshopInfo {
override fun label(): String = "Basics"
override fun code(): String = "ws-1"
}
class DecoratedInfo(private val delegate: WorkshopInfo) : WorkshopInfo by delegate {
override fun label(): String = "Featured: ${delegate.label()}"
}
fun main() {
val info = DecoratedInfo(BasicInfo())
println(info.label())
println(info.code())
}
Featured: Basics
ws-1
المخرج المتوقع Featured: Basics ثم ws-1. تُتجاوز label، ويُمرَّر استدعاء code إلى المفوض. لم يُترجم المصدر هنا، ولا يتضمن ضمانا لعمر مورد أو لسلوك التزامن.
لا تجعل هوية المفرد الحالة المعدلة آمنة
يوفّر تصريح object مثيلا مفردا مسمى، بينما ينشئ تعبير object كائنا مجهولا في موضع تقييمه. ينتمي companion object إلى صنف، ويُستخدم كثيرا للمصانع والثوابت المرتبطة به؛ ولا تصبح دواله تلقائيا طرق Java static عادية دون دعم التكامل المناسب. يظل المفرد ذو عداد قابل للتعديل حالة مشتركة تحتاج دلالات تزامن وإعادة ضبط. فضّل تمرير اعتماد مملوك عندما تحتاج الاختبارات أو نسخ التطبيق المستقلة حالة منفصلة. الإتاحة العامة قرار متعلق بالعمر وله كلفته، وليست بديلا عن تركيب واضح للاعتماديات.
الأخطاء الشائعة وإصلاحها
تحاول خاصية واجهة تخزين حقل خلفي.
- لماذا يحدث؟
- استُخدم عقد الواجهة كما لو كان صنفا يملك حالة مخزّنة.
- كيف تصلحه؟
- قدّم تصريح وصول مجردا، واجعل التخزين في الصنف المنفذ.
- كيف تتجنب تكراره؟
- افصل وصف القدرة عن ملكية الحالة التي تطبقها.
لا يؤثر تجاوز الغلاف في سلوك المفوض الداخلي.
- لماذا يحدث؟
- اختلط تمرير الاستدعاءات بوراثة التنفيذ الافتراضي.
- كيف تصلحه؟
- راجع استدعاءات المفوض لنفسه، وتجاوز السلوك المطلوب كاملا بصورة صريحة.
- كيف تتجنب تكراره؟
- اختبر التفاعلات، لا الاستدعاءات المباشرة للغلاف وحدها.
تتسرب حالة من اختبار إلى آخر عبر مفرد قابل للتعديل.
- لماذا يحدث؟
- اختير عمر عام دون قواعد لإعادة الضبط أو ملكية واضحة.
- كيف تصلحه؟
- احقن مثيلات مستقلة عندما يكون عزل الحالات مطلوبا.
- كيف تتجنب تكراره؟
- قلّل حالة المفرد، ووثّق أي تعديل مشترك لها.
اختبر فهمك
1. هل يغلق by تلقائيا مورد ملف يفوّض إليه الغلاف؟
اعرض الحل المشروح
لا. فهو يمرر أعضاء الواجهة المعلنة فقط. يجب تمثيل ملكية المورد وإغلاقه وتنفيذهما صراحة، بما في ذلك هل يملك الغلاف الكائن المفوض أم يملكه المستدعي. لا تستنتج سلوك العمر من قصر الصياغة. وعندما يوجد تنفيذ حقيقي، تحقّق من التنظيف عند حد المورد الفعلي.
2. متى يُفضَّل تعبير object على بديل اختبار مفرد مسمى؟
اعرض الحل المشروح
عندما يحتاج سيناريو محلي واحد تنفيذا صغيرا ببياناته الملتقطة الخاصة، دون حالة عامة مشتركة. وقد يكون صنف مسمى أوضح للسلوك المتكرر أو المعقد. اختر عمرا يطابق الاختبار، ولا تشارك أمثلة قابلة للتعديل بين حالات غير مرتبطة لمجرد تقليل عدد التصريحات.
أهم ما تعلمته
- تصف الواجهات القدرات، وتملك التنفيذات الحالة وعمرها.
- يمرّر التفويض السلوك دون إنشاء ضمانات أمان خفية.