الاستنساخ (طريقة جافا)

clone()هي دالة في لغة برمجة جافا تُستخدم لنسخ الكائنات . في جافا، تُعالج الكائنات من خلال متغيرات مرجعية، ولا يوجد مُعامل لنسخ الكائن نفسه - مُعامل الإسناد يُنسخ المرجع، وليس الكائن. تُوفر دالة clone() هذه الوظيفة المفقودة.

ملخص

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

clone()يعمل هذا الأسلوب كدالة إنشاء نسخ. عادةً ما يستدعي clone()أسلوب الفئة الأصلية للحصول على النسخة، وهكذا حتى يصل في النهاية إلى أسلوب الفئة Objectالأصلية clone(). يوفر الأسلوب الخاص clone()في الفئة الأساسيةObject آلية قياسية لتكرار الكائنات.

تقوم دالة الفئة بإنشاء نسخة من الكائن وإعادتها، بنفس الفئة وبنفس قيم جميع الحقول. ومع ذلك، فإنها تُطلق استثناءً ما لم يكن الكائن نسخة من فئة تُنفذ واجهة العلامة .Objectclone()Object.clone()CloneNotSupportedExceptionCloneable

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

صيغة استدعاء الدالة cloneفي جافا هي (بافتراض objأن متغير من نوع فئة يحتوي على clone()دالة عامة):

Object copy = obj.clone ( ) ;

أو عادة

MyClass copy = ( MyClass ) obj.clone ( ) ;

والذي يوفر عملية تحويل النوع اللازمة لتعيين Objectالمرجع العام الذي تم إرجاعه من cloneإلى مرجع لكائن MyClass.

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

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

البدائل

توجد بدائل لـ `require` clone()، أبرزها استخدام مُنشئ النسخ - وهو مُنشئ يقبل كمعامل نسخة أخرى من نفس الفئة - أو دالة المصنع . لا تكون هذه الطرق مناسبة دائمًا عندما يكون النوع الملموس للكائن المُستنسخ غير معروف مسبقًا. (مع ذلك، clone()غالبًا ما تكون `require` غير مناسبة أيضًا لنفس السبب، حيث أن معظم الفئات المجردة لا تُنفذ clone()دالة عامة).

كما أن استخدام التسلسل وإلغاء التسلسل هو بديل لاستخدام الاستنساخ.

نمط فردي

عند كتابة فئة باستخدام نمط Singleton ، لا يمكن أن يوجد سوى نسخة واحدة من تلك الفئة في الوقت نفسه. ونتيجة لذلك، يجب منع الفئة من إنشاء نسخة منها. ولمنع ذلك، يمكن تجاوز الدالة clone()باستخدام الكود التالي:

public Object clone () throws CloneNotSupportedException { throw new CloneNotSupportedException (); }

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

التسلسل الهرمي للطبقات

لتوفير كائن قابل للاستنساخ بشكل صحيح من أي نوع، يجب تعريف طريقة clone() وتنفيذها بشكل صحيح وفقًا للاتفاقية الموضحة في Object.clone().

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

مثال:

لاستدعاء الدالة clone() على المتغير varY1، وهو من النوع Y، يجب على Y أو أحد أسلاف Y تعريف دالة clone() عامة. في هذه الحالة، الصنف الأب X هو الذي يوفر دالة clone() العامة.

public class X implements Cloneable { public X clone () throws CloneNotSupportedException { return ( X ) super . clone (); } }public class Y extends X { }public class Z extends Y { }public class test1 { public void function () throws CloneNotSupportedException { Y varY1 = new Z (); Y varY2 = ( Y ) varY1 . clone (); } }

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

مثال:

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

public class X implements Cloneable { public X clone () throws CloneNotSupportedException { return ( X ) super . clone (); } }public class Y extends X { }public class ObjectABC implements Cloneable { public ObjectABC clone () throws CloneNotSupportedException { return ( ObjectABC ) super . clone (); } }public class Z extends Y { private ObjectABC someABC ;public Z clone () throws CloneNotSupportedException { Z newZ = ( Z ) super . clone (); newZ . someABC = someABC . clone ();return newZ ; } }public class test1 { public void function () throws CloneNotSupportedException { Y varY1 = new Z (); Y varY2 = ( Y ) varY1 . clone (); } }

المخاطر

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

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

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

الحقول النهائية

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

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

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

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

مراجع