نسخ الكائنات

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

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

طرق النسخ

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

لنفترض وجود كائن A يحتوي على الحقول xᵢ ( بتعبير أدق، لنفترض أن A سلسلة نصية و xᵢ مصفوفة من أحرفها). توجد استراتيجيات مختلفة لإنشاء نسخة من A، تُعرف بالنسخ السطحي والنسخ العميق . تسمح العديد من لغات البرمجة بالنسخ العام باستخدام إحدى هاتين الاستراتيجيتين أو كلتيهما، حيث تُعرّف إما عملية نسخ واحدة أو عمليتي نسخ سطحي وعميق منفصلتين . [ 1 ] تجدر الإشارة إلى أن النسخ السطحي للغاية هو استخدام مرجع إلى الكائن A الموجود، وفي هذه الحالة لا يوجد كائن جديد، بل مرجع جديد فقط.

يعود مصطلح النسخ السطحي والنسخ العميق إلى لغة Smalltalk -80. [ 2 ] وينطبق التمييز نفسه على مقارنة الكائنات من أجل المساواة: في الأساس، هناك فرق بين الهوية (نفس الكائن) والمساواة (نفس القيمة)، وهو ما يتوافق مع المساواة السطحية والمساواة العميقة (مستوى واحد) لمرجعين لكائنين، ولكن بعد ذلك ما إذا كانت المساواة تعني مقارنة حقول الكائن المعني فقط أو فك مرجعية بعض أو كل الحقول ومقارنة قيمها بدورها (على سبيل المثال، هل تتساوى قائمتان مرتبطتان إذا كان لهما نفس العقد، أو إذا كان لهما نفس القيم؟).

نسخة سطحية

يشير الحرفان A و B إلى منطقتين مختلفتين في الذاكرة.
مرجع متغير إلى مساحة ذاكرة مختلفة
إسناد المتغير B إلى A.
إسناد المتغير B إلى A.
المتغيرات التي تشير إلى نفس منطقة الذاكرة.
المتغيرات التي تشير إلى نفس منطقة الذاكرة.

إحدى طرق نسخ كائن ما هي النسخ السطحي . في هذه الحالة، يُنشأ كائن جديد B ، وتُنسخ قيم حقول A إلى B. [ 3 ] [ 4 ] [ 5 ] يُعرف هذا أيضًا باسم النسخ حقلًا بحقل ، [ 6 ] [ 7 ] [ 8 ] أو النسخ حقلًا بحقل ، أو نسخ الحقل . [ 9 ] إذا كانت قيمة الحقل عبارة عن مرجع لكائن (مثل عنوان ذاكرة )، فإنه ينسخ المرجع، وبالتالي يشير إلى نفس الكائن الذي يشير إليه A، وإذا كانت قيمة الحقل نوعًا أوليًا، فإنه ينسخ قيمة النوع الأولي. في اللغات التي لا تحتوي على أنواع أولية (حيث كل شيء عبارة عن كائن)، تكون جميع حقول النسخة B عبارة عن مراجع لنفس الكائنات التي تشير إليها حقول الكائن الأصلي A. وبالتالي، فإن الكائنات المشار إليها مشتركة ، لذلك إذا تم تعديل أحد هذه الكائنات (من A أو B)، فسيكون التغيير مرئيًا في الآخر. النسخ السطحي بسيط وسريع التنفيذ، حيث يمكن تنفيذه عادةً عن طريق نسخ البتات بدقة.

النسخ العميق

عملية نسخ عميقة جارية.
عملية نسخ عميقة جارية.
تم الانتهاء من النسخ العميق
تم الانتهاء من النسخ العميق.

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

مزيج

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

تطبيق

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

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

نسخة كسولة

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

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

النسخ الكسول مرتبط بالنسخ عند الكتابة .

في لغة C++ ، يتم إجراء النسخ الكسول بشكل افتراضي عند استدعاء مُنشئ النسخ .

في لغة جافا

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

على عكس لغة C++، يتم الوصول إلى الكائنات في جافا دائمًا بشكل غير مباشر عبر المراجع . لا تُنشأ الكائنات ضمنيًا أبدًا، بل تُمرر أو تُسند إليها دائمًا بواسطة متغير مرجعي. (تُمرر الدوال في جافا دائمًا بالقيمة ، ولكن القيمة المُمررة هي قيمة المتغير المرجعي). [ 11 ] تتولى آلة جافا الافتراضية إدارة عملية جمع البيانات المهملة، بحيث تُحذف الكائنات بعد أن يتعذر الوصول إليها. لا توجد طريقة تلقائية لنسخ أي كائن في جافا.

عادةً ما تتم عملية النسخ بواسطة دالة `clone()` الخاصة بالفئة. تقوم هذه الدالة بدورها باستدعاء دالة `clone()` الخاصة بالفئة الأصلية للحصول على نسخة، ثم تُنفذ أي إجراءات نسخ مخصصة. في النهاية، تصل العملية إلى دالة `clone()` الخاصة Objectبالفئة الأعلى، والتي تُنشئ نسخة جديدة من نفس الفئة وتنسخ جميع الحقول إلى النسخة الجديدة (نسخة سطحية). إذا تم استخدام هذه الدالة، يجب على الفئة تطبيق Cloneableواجهة `marker`، وإلا ستُصدر استثناءً يُفيد بأن النسخ غير مدعوم. بعد الحصول على نسخة من الفئة الأصلية، يُمكن لدالة `clone()` الخاصة بالفئة توفير إمكانية نسخ مخصصة، مثل النسخ العميق (أي تكرار بعض البنى التي يُشير إليها الكائن) أو إعطاء النسخة الجديدة مُعرّفًا فريدًا جديدًا.

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

من عيوب استخدام دالة `clone()` في الأنواع المجردة عدم إمكانية الوصول إليها في كثير من الأحيان . فمعظم الواجهات والفئات المجردة في جافا لا تُحدد دالة `clone()` عامة. وبالتالي، غالبًا ما تكون الطريقة الوحيدة لاستخدام هذه الدالة هي معرفة نوع الكائن، وهو ما يتعارض مع مبدأ التجريد الذي ينص على استخدام أكثر الأنواع عمومية. على سبيل المثال، إذا كان لدينا مرجع إلى قائمة في جافا، فلا يمكننا استدعاء `clone()` على هذا المرجع لأن `List` لا تُحدد دالة `clone()` عامة. تحتوي تطبيقات القوائم، مثل Array List و Linked List، عمومًا على دوال `clone()`، ولكن من غير العملي وغير الأمثل تجريدًا الاحتفاظ بنوع الكائن.

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

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

في إيفل

يمكن الوصول إلى كائنات وقت التشغيل في لغة إيفل إما بشكل غير مباشر عبر المراجع أو ككائنات موسعة تُضمّن حقولها داخل الكائنات التي تستخدمها. أي أن حقول الكائن تُخزّن إما خارجيًا أو داخليًا .

تحتوي فئة Eiffel ANYعلى ميزات لنسخ واستنساخ الكائنات، سواءً كان النسخ سطحيًا أو عميقًا. ترث جميع فئات Eiffel من هذه الفئة ANY، لذا فإن هذه الميزات متاحة في جميع الفئات، وهي قابلة للتطبيق على كلٍ من الكائنات المرجعية والكائنات الموسعة.

تُجري هذه copyالخاصية نسخًا سطحيًا، حقلًا بحقل، من كائن إلى آخر. في هذه الحالة، لا يتم إنشاء أي كائن جديد. إذا yتم نسخ الكائن إلى ، فسيتم الإشارة إلى xنفس الكائنات التي كان يشير إليها الكائن yقبل تطبيق الخاصية ، بعد اكتمالها.copyxcopy

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

تعتمد هذه الخاصية twinعلى الخاصية copy، والتي يمكن إعادة تعريفها في العناصر التابعة لها ANY، إذا لزم الأمر. وتكون نتيجة هذه الخاصية twinمن النوع المُثبَّت like Current.

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

بلغات أخرى

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

في لغة Objective-C ، ترث جميع الكائنات الدالتين ` copyrequire` و` mutableCopyrequire`، وهما مخصصتان لنسخ الكائنات؛ حيث تُستخدم الدالة `require` لإنشاء نوع قابل للتعديل من الكائن الأصلي. تستدعي هاتان الدالتان بدورهما الدالتين `require` copyWithZoneو` mutableCopyWithZonerequire` على التوالي لتنفيذ عملية النسخ. يجب على الكائن تنفيذ copyWithZoneالدالة المقابلة `require` لكي يكون قابلاً للنسخ.

في لغة OCaml ، تقوم دالة المكتبة Oo.copy بنسخ الكائن بشكل سطحي.

في لغة بايثون ، توفر وحدة النسخ في المكتبة نسخًا سطحية ونسخًا عميقة للكائنات باستخدام الدالتين `copy` و`deep` copy()على deepcopy()التوالي. [ 13 ] يمكن للمبرمجين تعريف دوال خاصة `copy` __copy__()و`deep` __deepcopy__()في الكائن لتوفير تنفيذ نسخ مخصص.

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

في لغة Rust ، يمكن للهياكل تنفيذ cloneالطريقة باستخدام Cloneالسمة. [ 14 ] [ 15 ]

في لغة بيرل ، يتم تخزين الهياكل المتداخلة باستخدام المراجع، وبالتالي يمكن للمطور إما أن يقوم بالتكرار على الهيكل بأكمله وإعادة الإشارة إلى البيانات أو استخدام الدالة dclone()من الوحدة النمطية Storable .

في لغة VBA ، تُعتبر عملية إسناد قيمة لمتغيرات من نوع معين Objectنسخة سطحية، بينما تُعتبر عملية إسناد قيمة لجميع الأنواع الأخرى (الأنواع العددية، والسلاسل النصية، والأنواع المُعرّفة من قِبل المستخدم، والمصفوفات) نسخة عميقة. لذا، Setتُشير الكلمة المفتاحية `--` في عملية الإسناد إلى نسخة سطحية، بينما Letتُشير الكلمة المفتاحية `--` (الاختيارية) إلى نسخة عميقة. لا توجد دالة مُدمجة لإنشاء نسخ عميقة للكائنات في VBA.

انظر أيضاً

ملحوظات

  1. 1 2 3 جروجونو وساكينين 2000 .
  2. غولدبرغ وروبسون 1983 ، الصفحات 97-99. "هناك طريقتان لإنشاء نسخ من كائن. ويكمن الفرق في ما إذا كانت قيم متغيرات الكائن تُنسخ أم لا. إذا لم تُنسخ القيم، فإنها تُشارك (shallowCopy)؛ وإذا نُسخت القيم، فإنها لا تُشارك (deepCopy)."
  3. "شرح النسخ السطحي مقابل النسخ العميق في لغة C++" . مؤرشف من الأصل بتاريخ 10 فبراير 2014. تم الاطلاع عليه بتاريخ 10 أبريل 2013 .
  4. "شرح النسخ السطحي مقابل النسخ العميق في .NET" .
  5. "شرح النسخ السطحي والعميق العام" . مؤرشف من الأصل بتاريخ 2016-03-04 . تم الاطلاع عليه بتاريخ 2013-04-10 .
  6. أساسيات لغة جافا: المجلد 1، صفحة 295
  7. جافا الفعالة ، الطبعة الثانية، ص 54
  8. " ما المقصود بنسخ الحقول بواسطة Object.clone()؟ Stack Overflow
  9. "جوش بلوخ يتحدث عن التصميم: حوار مع مؤلف كتاب "البرمجة الفعالة بلغة جافا"، بقلم بيل فينرز، جافا وورلد ، 4 يناير 2002، ص 13
  10. غولدبرغ وروبسون 1983 ، ص 97. "التنفيذ الافتراضي لـcopyهوshallowCopy. في الفئات الفرعية التي يجب أن ينتج عن النسخ فيها مزيج خاص من المتغيرات المشتركة وغير المشتركة، عادةً ما يتم إعادة تنفيذ الطريقة المرتبطة بالنسخ، بدلاً من الطريقة المرتبطة بـshallowCopyأوdeepCopy."
  11. "تمرير المعلومات إلى طريقة أو مُنشئ" . تم الاطلاع عليه بتاريخ 8 أكتوبر 2013 .
  12. مكتبة الاستنساخ العميق في جافا
  13. وحدة نسخ بايثون
  14. "الاستنساخ في std::clone - Rust" . doc.rust-lang.org . تم الاطلاع عليه في 3 نوفمبر 2025 .
  15. "استنساخ - Rust بالأمثلة" . doc.rust-lang.org . تم الاطلاع عليه في 3 نوفمبر 2025 .

مراجع

  • غولدبيرغ، أديل ؛ روبسون، ديفيد (1983). سمول توك-80: اللغة وتطبيقها . بالو ألتو، كاليفورنيا: مركز أبحاث زيروكس بالو ألتو. ISBN 978-0-201-11371-6.
  • غروغونو، بيتر؛ ساكينين، ماركو (12 مايو 2000). "النسخ والمقارنة: المشكلات والحلول" (ملف PDF) . في: إليسا بيرتينو (محررة). سلسلة محاضرات في علوم الحاسوب . ECOOP 2000 - البرمجة كائنية التوجه. المجلد  1850. سبرينغر برلين هايدلبرغ. الصفحات 226-250 . doi : 10.1007/3-540-45102-1_11 . مؤرشف من النسخة الأصلية (PDF) بتاريخ 4 مارس 2016. تاريخ الاسترجاع: 23 يونيو 2015 .