Kotlin / أسس اللغة والحالة الصريحة / الدرس 5
نمذجة الحالة بالأصناف وأصناف البيانات
استخدم المنشئات والتحقق في init والخصائص والمساواة والنسخ، مع مراعاة التغليف والنسخ السطحي للحالة.
في هذه الصفحة
أهداف الدرس
- اشرح أي خصائص المنشئ تشارك في المساواة والنسخ المولّدين لصنف البيانات.
- امنع الحالة غير الصالحة دون افتراض أن val تجمّد الكائنات الداخلية المشار إليها.
ضع الحالة المطلوبة في المنشئ الأساسي
يجمع الصنف الحالة والسلوك. تصبح معاملات المنشئ الأساسي خصائص فقط عند تعريفها باستخدام val أو var؛ ويمكن استخدام المعامل العادي للتهيئة أيضا. تعمل كتلة init أثناء الإنشاء وتستطيع فحص القيود. فضّل نموذجا يصبح صالحا فور إنشائه على نموذج يتطلب من المستدعي تذكّر عدة خطوات إعداد. ويهم ترتيب التهيئة عندما تعتمد الخصائص على حالة سابقة. تجنب استدعاء سلوك قابل للتجاوز أثناء البناء، لأن الصنف الفرعي قد لا يكون قد هيّأ البيانات التي يتوقعها العضو المتجاوز بعد.
احفظ القاعدة بانتقالات حالة مضبوطة
يشترط الصنف في المثال سعة ابتدائية غير سالبة، ويتيح قراءة remaining للعامة مع جعل setter خاصا. تعيد reserve القيمة false عند نفاد المقاعد، بدلا من السماح بحالة سالبة. ينمذج هذا مالكا محليا واحدا، وليس تخصيصا آمنا بين الخيوط أو حجزا دائما. يحتاج مستدعيان متزامنان إلى تصميم مزامنة وتخزين إضافي. يفيد التغليف لأن المستدعي لا يستطيع تجاوز قاعدة الإنقاص بإسناد عادي للخاصية، لكنه لا ينشئ ضمانات خارج العملية ولا يحمي الكائنات القابلة للتعديل المتداخلة تلقائيا.
class SeatCounter(initial: Int) {
var remaining: Int = initial
private set
init { require(initial >= 0) { "negative capacity" } }
fun reserve(): Boolean {
if (remaining == 0) return false
remaining -= 1
return true
}
}
fun main() {
val counter = SeatCounter(1)
println(counter.reserve())
println(counter.reserve())
println(counter.remaining)
}
true
false
0
المخرجات المتوقعة هي true ثم false ثم 0. يمنع الصنف السعة السالبة عبر عملياته العامة المعلنة في هذا المثال أحادي الخيط فقط. لم يُترجم البرنامج ولم يُختبر تزامنه هنا.
استخدم data class للسجل ذي دلالة القيمة
يولّد data class عمليات مفيدة مثل equals وhashCode وtoString ودوال component والعملية copy، اعتمادا على خصائص منشئه الأساسي. لا تشارك الخصائص المعلنة داخل جسم الصنف فقط في هذه العمليات القيمية المولّدة. قد يفاجئ ذلك المقارنة أو مفتاح التخزين إذا وُضع جزء مهم من الهوية خارج المنشئ. استخدم == للمساواة البنيوية وفقا لـequals، و=== لهوية المرجع. وللمصفوفات اعتبارات خاصة عند مقارنة المحتوى؛ فلا تفترض أن كل حاوية تقارن محتوياتها كلها بصورة عميقة وبالطريقة نفسها.
نسخ صنف البيانات عملية سطحية
تنشئ copy مثيلا آخر، وتستخدم قيم الخصائص الحالية افتراضيا لمعاملات المنشئ التي لم تتغير. إذا احتوت خاصية قائمة قابلة للتعديل، فقد يظل المثيلان يشيران إلى القائمة نفسها. فضّل خصائص ذات دلالة قيمة عندما يمثل data class سجلا مستقرا. يستخدم المثال نصوصا وأعدادا صحيحة، لذلك يكون معنى نسخ عدد المقاعد مباشرا. ويجب أن تجتاز النسخة تحقق المنشئ أيضا إن وجد. ليست copy تحديثا لقاعدة بيانات أو استنساخا عميقا أو دليلا على تكرار موارد الكائن الخارجية بطريقة آمنة.
data class Reservation(val workshopId: String, val seats: Int)
fun main() {
val first = Reservation("ws-6", 1)
val equalValue = Reservation("ws-6", 1)
val changed = first.copy(seats = 2)
println(first == equalValue)
println(first === equalValue)
println(changed.seats)
println(first.seats)
}
true
false
2
1
المخرجات المتوقعة هي true ثم false ثم 2 ثم 1. قد يتساوى مثيلان مختلفان بالقيمة. ويترك تغيير عدد المقاعد في النسخة خاصية العدد الصحيح في الأصل دون تعديل. لا يُدعى هنا اختبار بالمترجم.
اختر الوراثة وقابلية التعديل عمدا
تكون أصناف Kotlin وأعضاؤها نهائية افتراضيا، ما لم تُفتح أو تنطبق قاعدة أخرى. يشجّع ذلك عقود امتداد صريحة بدلا من التجاوز غير المقصود. تستطيع الواجهة التعبير عن سلوك دون اشتراط صنف أب واحد ذي حالة مشتركة. فضّل التركيب عندما يستخدم كائن قدرة كائن آخر، بدلا من أن يكون بديلا حقيقيا عنه. تصبح الخصائص العامة القابلة للتعديل جزءا من سلوك النموذج وقد تضعف قيوده إذا كانت بلا ضبط. ابدأ بمنشئ صغير وعمليات ضيقة، وأضف نقاط الامتداد عندما يحدد استعمال حقيقي ما يجوز للتجاوز تغييره.
الأخطاء الشائعة وإصلاحها
يتساوى سجلا بيانات رغم اختلاف خصائص في جسم الصنف.
- لماذا يحدث؟
- تأخذ المساواة المولدة خصائص المنشئ الأساسي في الحسبان دون خصائص الجسم الأخرى.
- كيف تصلحه؟
- انقل الحالة التي تحدد القيمة إلى المنشئ، أو عرّف مساواة مقصودة تناسب النموذج.
- كيف تتجنب تكراره؟
- اختبر المقارنة بكل حقل له أثر في معنى القيمة داخل المجال.
يؤدي تعديل قائمة النسخة إلى تغيير السجل الأصلي.
- لماذا يحدث؟
- احتفظت copy بمرجع مشترك قابل للتعديل بدلا من نسخه بعمق.
- كيف تصلحه؟
- استخدم عناصر غير قابلة للتعديل أو سياسة نسخ صريحة مناسبة للمحتوى.
- كيف تتجنب تكراره؟
- وثّق دلالات النسخ السطحي عند حدود النموذج.
يسمح setter عام بجعل السعة سالبة.
- لماذا يحدث؟
- تجاوز تعديل الحالة العملية التي تحفظ القيد.
- كيف تصلحه؟
- قيّد الإسناد وقدّم عمليات متحققة لتغيير الحالة.
- كيف تتجنب تكراره؟
- افحص أن كل انتقال حالة عام يحافظ على القيد المطلوب.
اختبر فهمك
1. لماذا لا يكفي SeatCounter لخدمة حجز حقيقية متعددة المستخدمين؟
اعرض الحل المشروح
توجد حالته في ذاكرة كائن واحد، ولا يمثل الفحص والإنقاص فيه معاملة دائمة بين الطلبات. كما يفتقر إلى هوية الطلب والتخزين والتحكم في التزامن والاستعادة. يعلّم المثال التغليف فقط. يجب على الخدمة حماية قيد السعة المرجعي بتنفيذ يناسب مخزنها ونموذج تنفيذها، ولا يمكن استنتاج تلك الحماية من الصنف المحلي.
2. متى تستخدم خاصية في المنشئ الأساسي لصنف بيانات الكلمة val؟
اعرض الحل المشروح
استخدم val عندما لا ينبغي إعادة إسناد الخاصية بعد الإنشاء. يدعم ذلك السجلات ذات دلالة القيمة والنسخ المتوقع، لكن الكائن القابل للتعديل الذي تشير إليه الخاصية قد يظل يتغير داخليا. افحص كامل الحالة التي يمكن الوصول إليها قبل ادعاء الثبات العميق أو أمان مشاركتها بين مهام متزامنة.
أهم ما تعلمته
- ابن كائنات صالحة منذ الإنشاء واضبط انتقالات حالتها.
- تعتمد عمليات قيمة data class على خصائص المنشئ وعلى مراجع تُنسخ بصورة سطحية.