كائنات قابلة للتغيير مقابل كائنات غير قابلة للتغيير

StackOverflow https://stackoverflow.com/questions/214714

  •  03-07-2019
  •  | 
  •  

سؤال

أحاول أن أضع رأسي حول الأشياء القابلة للتغيير مقابل الأشياء غير القابلة للتغيير.إن استخدام الكائنات القابلة للتغيير يؤدي إلى الكثير من الضغط السيئ (على سبيل المثال.إرجاع مجموعة من السلاسل من إحدى الطرق) ولكني أواجه مشكلة في فهم التأثيرات السلبية لذلك.ما هي أفضل الممارسات حول استخدام الكائنات القابلة للتغيير؟هل يجب عليك تجنبها كلما أمكن ذلك؟

هل كانت مفيدة؟

المحلول

حسنًا ، هناك جوانبان لهذا. رقم واحد ، يمكن أن تسبب الكائنات القابلة للتغيير بدون هوية مرجعية الحشرات في أوقات غريبة. على سبيل المثال ، فكر في أ Person بين مع قائم على القيمة equals طريقة:

Map<Person, String> map = ...
Person p = new Person();
map.put(p, "Hey, there!");

p.setName("Daniel");
map.get(p);       // => null

ال Person يتم "فقدان" المثيل في الخريطة عند استخدامه كمفتاح لأنه hashCode واستندت المساواة على قيم قابلة للتغيير. تغيرت هذه القيم خارج الخريطة وأصبحت جميع التجزئة عفا عليها الزمن. يحب المنظرون القيثارة في هذه النقطة ، لكن في الممارسة العملية لم أجدها مشكلة كبيرة.

جانب آخر هو "المعنى" المنطقي للرمز الخاص بك. هذا مصطلح صعب لتحديده ، يشمل كل شيء من قابلية القراءة إلى التدفق. بشكل عام ، يجب أن تكون قادرًا على النظر إلى قطعة من الكود وفهم ما تفعله بسهولة. لكن الأهم من ذلك ، يجب أن تكون قادرًا على إقناع نفسك بأنها تفعل ما تفعله بشكل صحيح. عندما يمكن أن تتغير الكائنات بشكل مستقل عبر "مجالات" رمز مختلف ، يصبح من الصعب في بعض الأحيان تتبع ما هو المكان ولماذا ("العمل العصبي على مسافة"). يعد هذا مفهومًا أكثر صعوبة ، لكنه شيء يواجه غالبًا في بنيات أكبر وأكثر تعقيدًا.

أخيرًا ، الكائنات القابلة للتغيير القاتل في المواقف المتزامنة. كلما قمت بالوصول إلى كائن قابل للتغيير عن مؤشرات ترابط منفصلة ، يجب عليك التعامل مع القفل. هذا يقلل من الإنتاجية ويجعل الكود الخاص بك بشكل كبير أكثر صعوبة للحفاظ عليها. ينفخ النظام المعقد بما فيه الكفاية هذه المشكلة حتى الآن غير متناسبة بحيث يصبح من المستحيل تقريبًا الحفاظ عليه (حتى بالنسبة لخبراء التزامن).

الأشياء غير القابلة للتغيير (وعلى وجه الخصوص ، مجموعات غير قابلة للتغيير) تجنب كل هذه المشكلات. بمجرد أن تدور حول كيفية عملها ، سوف يتطور الكود إلى شيء يسهل قراءته ، وأسهل في الحفاظ عليه وأقل عرضة للفشل بطرق غريبة ولا يمكن التنبؤ بها. من الأسهل اختبار الكائنات غير القابلة للتغيير ، ليس فقط إلى قابلية السخرية السهلة ، ولكن أيضًا أنماط التعليمات البرمجية التي تميل إلى فرضها. باختصار ، إنهم ممارسة جيدة في كل مكان!

مع ذلك ، أنا بالكاد متعصب في هذا الأمر. بعض المشكلات لا تصمم بشكل جيد عندما يكون كل شيء غير قابل للتغيير. لكنني أعتقد أنه يجب أن تحاول دفع أكبر قدر ممكن من الكود في هذا الاتجاه ، على افتراض أنك تستخدم لغة تجعل هذا رأيًا قابلًا للتطبيق (C/C ++ يجعل هذا الأمر صعبًا للغاية ، وكذلك Java) . باختصار: تعتمد المزايا على مشكلتك إلى حد ما ، لكنني أميل إلى تفضيل الثبات.

نصائح أخرى

الأشياء غير القابلة للتغيير مقابل مجموعات غير قابلة للتغيير

واحدة من النقاط الدقيقة في النقاش حول الأشياء القابلة للتغيير مقابل الأشياء القابلة للتغيير هي إمكانية توسيع مفهوم التثبات إلى المجموعات. كائن ثابت هو كائن يمثل غالبًا بنية منطقية واحدة للبيانات (على سبيل المثال سلسلة غير قابلة للتغيير). عندما يكون لديك إشارة إلى كائن ثابت ، فإن محتويات الكائن لن تتغير.

مجموعة ثابتة هي مجموعة لا تتغير أبدًا.

عندما أقوم بإجراء عملية على مجموعة قابلة للتغيير ، أقوم بتغيير المجموعة في مكانها ، وستشهد جميع الكيانات التي لديها إشارات إلى المجموعة التغيير.

عندما أقوم بإجراء عملية على مجموعة غير قابلة للتغيير ، يتم إرجاع مرجع إلى مجموعة جديدة تعكس التغيير. جميع الكيانات التي لديها إشارات إلى الإصدارات السابقة من المجموعة لن ترى التغيير.

لا تحتاج التطبيقات الذكية بالضرورة إلى نسخ (استنساخ) المجموعة بأكملها من أجل توفير هذا الثبات. إن أبسط مثال هو المكدس الذي تم تنفيذه كقائمة مرتبطة منفردة وعمليات الدفع/البوب. يمكنك إعادة استخدام جميع العقد من المجموعة السابقة في المجموعة الجديدة ، وإضافة عقدة واحدة فقط للدفع ، ولا استنساخ العقد للبوب. من ناحية أخرى ، فإن عملية Push_tail في قائمة مرتبطة منفردة ، ليست بسيطة أو فعالة.

غير قابل للتغيير مقابل المتغيرات/المراجع قابلة للتغيير

تأخذ بعض اللغات الوظيفية مفهوم الثبات في الاعتبار المراجع نفسها ، مما يسمح فقط بمهمة مرجعية واحدة.

  • في إرلانج هذا صحيح لجميع "المتغيرات". يمكنني فقط تعيين كائنات إلى مرجع مرة واحدة. إذا كنت أعمل على مجموعة ، فلن أتمكن من إعادة تعيين المجموعة الجديدة إلى المرجع القديم (الاسم المتغير).
  • تقوم Scala أيضًا ببناء هذا في اللغة مع إعلان جميع المراجع var أو فال, ، فقط Vals كونها مهمة واحدة وتعزيز نمط وظيفي ، ولكن Vars تسمح بهيكل برنامج أكثر تشبه C أو يشبه Java.
  • مطلوب إعلان VAR/VAL ، في حين تستخدم العديد من اللغات التقليدية المعدلات الاختيارية مثل نهائي في جافا و مقدار ثابت في ج.

سهولة التطوير مقابل الأداء

دائمًا ما يكون سبب استخدام كائن غير قابل للتغيير هو تعزيز البرمجة الحرة للأثر الجانبي والتفكير البسيط حول الكود (خاصة في بيئة متزامنة للغاية/متوازية). لا داعي للقلق بشأن البيانات الأساسية التي يتم تغييرها بواسطة كيان آخر إذا كان الكائن غير قابل للتغيير.

العيب الرئيسي هو الأداء. هنا كتابة على اختبار بسيط قمت به في جافا مقارنة بعض الكائنات غير القابلة للتغيير مقابل مشكلة في لعبة.

إن مشكلات الأداء في العديد من التطبيقات ، ولكن ليس كلها ، وهذا هو السبب في أن العديد من الحزم العددية الكبيرة ، مثل فئة Array Numpy في Python ، تسمح بتحديثات في مكانها للمصفوفات الكبيرة. سيكون هذا مهمًا لمجالات التطبيق التي تستفيد من عمليات المصفوفة الكبيرة والمتجهات. هذه المشكلات الكبيرة المتوازية والمكثفة الحسابية تحقق تسريعًا كبيرًا من خلال العمل في مكانها.

تحقق من منشور المدونة هذا: http://www.yegor256.com/2014/06/09/Objects-hould-be-immutable.html. وهذا ما يفسر سبب كون الأشياء غير القابلة للتغيير أفضل من قابلة للتغيير. بالمختصر:

  • الكائنات غير القابلة للتغيير هي أبسط لبناء واختبار واستخدام
  • الكائنات غير القابلة للتغيير هي دائما آمنة مؤشرات الخيط
  • أنها تساعد على تجنب الاقتران الزمني
  • استخدامهم خالي من التأثير الجانبي (لا توجد نسخ دفاعية)
  • يتم تجنب مشكلة تطبيب الهوية
  • لديهم دائما فشل الذرة
  • هم أسهل بكثير في ذاكرة التخزين المؤقت

الأشياء غير القابلة للتغيير هي مفهوم قوي للغاية. إنهم يسلبون الكثير من عبء محاولة الحفاظ على الأشياء/المتغيرات متسقة لجميع العملاء.

يمكنك استخدامها في كائنات منخفضة المستوى غير الأشكال - مثل فئة Cpoint - والتي يتم استخدامها في الغالب مع دلالات القيمة.

أو يمكنك استخدامها لمستوى عالٍ ، واجهات متعددة الأشكال - مثل Ifunction التي تمثل وظيفة رياضية - يتم استخدامها حصريًا مع دلالات الكائن.

أكبر ميزة: التثبيت + الكائنات الدلالية + المؤشرات الذكية تجعل ملكية الكائنات غير قضية ، جميع عملاء الكائن لديهم نسخة خاصة بهم افتراضيًا. ضمنيًا هذا يعني أيضًا السلوك الحتمي في وجود التزامن.

العيب: عند استخدامها مع الكائنات التي تحتوي على الكثير من البيانات ، يمكن أن يصبح استهلاك الذاكرة مشكلة. قد يكون الحل لهذا هو الحفاظ على العمليات على كائن رمزي ، وإجراء تقييم كسول. ومع ذلك ، يمكن أن يؤدي ذلك بعد ذلك إلى سلاسل الحسابات الرمزية ، والتي قد تؤثر سلبًا على الأداء ، إذا لم يتم تصميم الواجهة لاستيعاب العمليات الرمزية. شيء لتجنبه بالتأكيد في هذه الحالة هو إعادة قطع الذاكرة الضخمة من طريقة ما. بالاقتران مع العمليات الرمزية بالسلاسل ، قد يؤدي هذا إلى استهلاك الذاكرة الهائل وتدهور الأداء.

لذا فإن الأشياء غير القابلة للتغيير هي بالتأكيد طريقة التفكير الأساسية في التصميم الموجهة للكائن ، لكنها ليست عقيدة. إنهم يحلون الكثير من المشكلات للعملاء من الكائنات ، ولكنهم يقومون أيضًا بإنشاء الكثير ، وخاصة للمنفذين.

يجب عليك تحديد اللغة التي تتحدث عنها. بالنسبة لللغات ذات المستوى المنخفض مثل C أو C ++ ، أفضل استخدام كائنات قابلة للتغيير للحفاظ على المساحة وتقليل انقطاع الذاكرة. في اللغات ذات المستوى الأعلى ، تسهل الكائنات غير القابلة للتغيير التفكير في سلوك الكود (وخاصة الكود متعدد الخيوط) لأنه لا يوجد "عمل عصبي على مسافة".

الكائن القابل للتغيير هو مجرد كائن يمكن تعديله بعد إنشائه/إنشاء مثيل له ، مقابل كائن ثابت لا يمكن تعديله (انظر صفحة ويكيبيديا حول هذا الموضوع). مثال على ذلك في لغة البرمجة هو قوائم Pythons و tuples. يمكن تعديل القوائم (على سبيل المثال ، يمكن إضافة عناصر جديدة بعد إنشائها) في حين أن Tuples لا يمكن.

لا أعتقد حقًا أن هناك إجابة واضحة حول أي واحد أفضل لجميع المواقف. كلاهما لديه أماكنهم.

إذا كان نوع الفئة قابلاً للتغيير، فيمكن أن يكون للمتغير من هذا النوع عدد من المعاني المختلفة.على سبيل المثال، لنفترض كائن foo لديه مجال int[] arr, ، وفيه إشارة إلى أ int[3] حاملاً الأرقام {5، 7، 9}.على الرغم من أن نوع الحقل معروف، إلا أن هناك أربعة أشياء مختلفة على الأقل يمكن أن يمثلها:

  • مرجع محتمل أن يكون مشتركًا، حيث يهتم جميع حامليه فقط بتغليف القيم 5 و7 و9.لو foo يريد arr لتغليف قيم مختلفة، يجب استبدالها بمصفوفة مختلفة تحتوي على القيم المطلوبة.إذا أراد أحد عمل نسخة منه foo, ، يجوز للمرء أن يعطي النسخة إما إشارة إلى arr أو مصفوفة جديدة تحمل القيم {1،2،3}، أيهما أكثر ملاءمة.

  • المرجع الوحيد، في أي مكان في الكون، إلى مصفوفة تحتوي على القيم 5 و7 و9.مجموعة من ثلاثة مواقع تخزين تحمل حاليًا القيم 5 و7 و9؛لو foo إذا أراد أن يقوم بتغليف القيم 5 و8 و9، فيمكنه إما تغيير العنصر الثاني في تلك المصفوفة أو إنشاء مصفوفة جديدة تحتوي على القيم 5 و8 و9 والتخلي عن العنصر القديم.لاحظ أنه إذا أراد أحد عمل نسخة من foo, ، يجب على المرء أن يحل محل النسخة arr مع الإشارة إلى مجموعة جديدة من أجل foo.arr لتبقى المرجع الوحيد لتلك المصفوفة في أي مكان في الكون.

  • إشارة إلى مجموعة يملكها البعض آخر الكائن الذي تعرض له foo لسبب ما (على سبيل المثال.ربما يريد foo لتخزين بعض البيانات هناك).في هذا السيناريو، arr لا يقوم بتغليف محتويات المصفوفة، بل يقوم بتغليفها هوية.لأن استبدال arr مع الإشارة إلى مجموعة جديدة من شأنه أن يغير معناها تماما، نسخة من foo يجب أن يحمل مرجعًا إلى نفس المصفوفة.

  • إشارة إلى مجموعة منها foo هو المالك الوحيد، ولكن المراجع التي يحتفظ بها كائن آخر لسبب ما (على سبيل المثاليريد أن يكون لديه كائن آخر لتخزين البيانات هناك - الجانب الآخر من الحالة السابقة).في هذا السيناريو، arr يغلف كلاً من هوية المصفوفة ومحتوياتها.استبدال arr مع الإشارة إلى مصفوفة جديدة من شأنه أن يغير معناها تمامًا، ولكن وجود نسخة مستنسخة arr تشير إلى foo.arr من شأنه أن ينتهك افتراض ذلك foo هو المالك الوحيد.وبالتالي لا توجد طريقة للنسخ foo.

نظريا، int[] يجب أن يكون نوعًا لطيفًا وبسيطًا ومحددًا جيدًا، ولكن له أربعة معاني مختلفة جدًا.على النقيض من ذلك، الإشارة إلى كائن غير قابل للتغيير (على سبيل المثال. String) بشكل عام له معنى واحد فقط.الكثير من "قوة" الأشياء غير القابلة للتغيير تنبع من هذه الحقيقة.

إذا قمت بإرجاع مراجع صفيف أو سلسلة ، فيمكن أن يعدل العالم الخارجي المحتوى في هذا الكائن ، وبالتالي يجعله ككائن قابل للتغيير (قابل للتعديل).

لا يمكن تغيير الوسائل غير القابلة للتغيير ، والتعني القابل للتغيير يمكنك التغيير.

الكائنات مختلفة عن البدائية في جافا. تم تصميم البدائل في أنواع (منطقية ، int ، إلخ) والكائنات (الفئات) هي أنواع تم إنشاؤها للمستخدم.

يمكن أن تكون البدائية والكائنات قابلة للتغيير أو غير قابلة للتغيير عند تعريفها على أنها متغيرات عضو داخل تنفيذ الفصل.

الكثير من الناس يعتقدون أن البدائل ومتغيرات الكائنات التي لها تعديل نهائي أمامهم غير قابلة للتغيير ، ومع ذلك ، فإن هذا ليس صحيحًا تمامًا. حتى النهائي لا يعني تقريبا غير قابل للتغيير للمتغيرات. انظر مثال هنا
http://www.siteconsortium.com/h/d0000f.php.

متقلب يتم تمرير الحالات بالرجوع إليها.

ثابت يتم تمرير الحالات بالقيمة.

مثال تجريدي. لنفترض وجود ملف مسمى txtfile في جهاز HDD الخاص بي. الآن ، عندما تسأل txtfile مني ، يمكنني إعادته في وضعين:

  1. إنشاء اختصار ل txtfile واختصار PAS لك ، أو
  2. خذ نسخة txtfile و PAS نسخ لك.

في الوضع الأول ، تم إرجاعه txtfile هو ملف قابل للتغيير ، لأنه عندما تقوم بتغييرات في ملف الاختصار ، يمكنك إجراء تغييرات في الملف الأصلي أيضًا. ميزة هذا الوضع هي أن كل اختصار تم إرجاعه يتطلب ذاكرة أقل (على ذاكرة الوصول العشوائي أو في HDD) والحرمان هو أن الجميع (ليس فقط أنا ، المالك) لديهم أذونات لتعديل محتوى الملف.

في الوضع الثاني ، عاد txtfile هو ملف ثابت ، لأن جميع التغييرات في الملف المستلم لا تشير إلى الملف الأصلي. ميزة هذا الوضع هي أنه فقط أنا (المالك) يمكنه تعديل الملف الأصلي والحرمان هو أن كل نسخة يتم إرجاعها المطلوبة (في ذاكرة الوصول العشوائي أو في HDD).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top