الفرق بين المراجعتين لصفحة: «Design Patterns/prototype»
أسامه-دمراني (نقاش | مساهمات) 2.1 إضافة محتوى |
أسامه-دمراني (نقاش | مساهمات) 2.2 إضافة محتوى |
||
سطر 122: | سطر 122: | ||
يحدث هذا كثيرًا حين تعمل شيفرتك مع كائنات ممررة إليك من شيفرة خارجية عبر واجهة ما، ولا تعرف الفئات الحقيقية لتلك الكائناتن ولا يمكنك الاعتماد عليها حتى لو أردت. فيوفر نمط النموذج الأولي ها هنا لشيفرة العميل واجهة عامة للعمل مع كل الكائنات التي تدعم الاستنساخ، وتلك الواجهة تجعل شيفرة العميل مستقلة عن الفئات الحقيقية للكائنات التي تستنسخها. | يحدث هذا كثيرًا حين تعمل شيفرتك مع كائنات ممررة إليك من شيفرة خارجية عبر واجهة ما، ولا تعرف الفئات الحقيقية لتلك الكائناتن ولا يمكنك الاعتماد عليها حتى لو أردت. فيوفر نمط النموذج الأولي ها هنا لشيفرة العميل واجهة عامة للعمل مع كل الكائنات التي تدعم الاستنساخ، وتلك الواجهة تجعل شيفرة العميل مستقلة عن الفئات الحقيقية للكائنات التي تستنسخها. | ||
استخدم النمط حين تريد تقليل عدد الفئات الفرعية التي لا تختلف إلا في طريقة بدء كائناتها، | استخدم النمط حين تريد تقليل عدد الفئات الفرعية التي لا تختلف إلا في طريقة بدء كائناتها، فقد يكون أحدهم قد أنشأ تلك الفئات الفرعية ليتمكن من إنشاء كائنات بإعدادات محددة. | ||
يسمح لك نمط النموذج الأولي باستخدام مجموعة كائنات مسبقة البناء ومُعدَّة بطرق مختلفة، يسمح لك باستخدامها كنماذج أولية. وبدلًا من تمثيل فئة فرعية تطابق بعض الإعدادات، فإن العميل يمكنه البحث ببساطة عن النموذج الأولي المناسب ويستنسخه. | |||
== كيفية الاستخدام == | |||
# أنشئ واجهة النموذج الأولي وصرّح عن أسلوب <code>clone</code> فيها، أو أضف الأسلوب إلى كل الفئات في الهرمية الحالية للفئات إن كانت لديك. | |||
# يجب أن تعرِّف فئة النموذج الأولي المنشئ البديل الذي يقبل كائنًا من تلك الفئة كوسيط، ويجب أن ينسخ المنشئ فيم كل الحقول المعرَّفة في تلك الفئة من الكائن المُمرَّر إلى النسخة المنشأة حديثًا. وإن كنت تغير فئة فرعية فيجب أن تستدعي المنشئ الأساسي (parent constructor) ليسمح للفئة الأساسية (superclass) بتولي عملية استنساخ حقولها الخاصة. أما إن كانت اللغة البرمجية التي تستخدمها لا تدعم التحميل الزائد (overloading)، فيمكنك أن تعرِّف أسلوبًا خاصًا لنسخ بيانات الكائن، ويمكنك استخدام المنشئ (constructor) كمكان مناسب لفعل ذلك بسبب أنه يسلم الكائن الناتج مباشرة بعد استدعاء معامِل <code>new</code>. | |||
# يتكون أسلوب الاستنساخ عادة من سطر واحد: تشغيل معامِل <code>new</code> مع النسخة النموذجية من المنشئ، لاحظ أنه يجب على كل فئة أن تتخطى أسلوب الاستنساخ وتستخدم اسم فئتها مع معامل <code>new</code>، وإلا فإن أسلوب الاستنساخ قد ينتج كائنًا من الفئة الأم. | |||
# أنشئ سجل نموذج أولي مركزي لتخزين فهرس بالنماذج الأولية التي تستخدم بكثرة. | |||
يمكنك استخدام السجل كفئة مصنع جديد أو وضعه في فئة النموذج الأولي الأساسي مع أسلوب ثابت (static method) لجلب النموذج الأولي، يجب أن يبحث هذا الأسلوب عن النموذج الأولي بناءً على معيار البحث الذي تمرره شيفرة العميل إلى الأسلوب، وقد يكون معيار البحث نصًا بسيطًا أو مجموعة معامِلات بحث معقدة. وعند العثور على النموذج الأولي المناسب فإن السجل يستنسخه ويعيد النسخة إلى العميل. | |||
وأخيرًا، استبدل الاستدعاءات المباشرة إلى منشئات الفئات الفرعيةباستدعاءات إلى أسلوب المصنع لسجل النموذج الأولي. | |||
== المزايا والعيوب == | |||
=== المزايا === | |||
* يمكنك استنساخ الكائنات دون ربطها بفئاتها الحقيقية. | |||
* يمكنك التخلص من شيفرة البدء المتكررة لصالح نسخ نماذج أولية مبنية مسبقًا. | |||
* يمكنك إنتاج كائنات معقدة بشكل أيسر. | |||
* تحصل على بديل للاكتساب (Inheritance) عند التعامل مع تجهيزات الإعدادات المسبقة للكائنات المعقدة. | |||
=== العيوب === | |||
* قد يكون من الصعب استنساخ الكائنات المعقدة التي لديها مراجع حِلَقية (circular references). | |||
== العلاقات مع الأنماط الأخرى == | |||
* تبدأ العديد من التصميمات مستخدمة أسلوب المصنع بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص عبر الفئات الفرعية، ثم تتطور إلى أنماط المصنع المجرد والنموذج الأولي والباني، إذ أنها أكثر مرونة لكنها معقدة أكثر في المقابل. | |||
* تُبنى فئات المصنع المجرد عادة على مجموعة من أساليب المصنع، لكنك تستطيع استخدام نمط النموذج الأولي لتركيب الأساليب على تلك الفئات. | |||
* التصاميم التي تستخدم نمط المركَّب والمزخرِف قد تستفيد من استخدام نمط النموذج الأولي، إذ يسمح باستخدام بُنى معقدة بدلًا من إعادة إنشائها من الصفر. | |||
* لا يبنى نمط النموذج الأولي على الاكتساب (inheritance) لذا ليس له مساوئه، لكن من الناحية الأخرى فإن النموذج الأولي يتطلب عملية بدء (initialization) معقدة للكائن المستنسخ. في المقابل، أسلوب المصنع مبني على الاكتساب لكنه لا يتطلب خطوة بدء. | |||
* أحيانًا قد يكون نمط النموذج الأول بديلًا بسيطًا لنمط التذكرة (Memento)، حين يكون الكائن، الحالة التي تريد تخزينها في السجل، بسيطة للغاية وليس لديها روابط إلى مصادر خارجية أو أن الروابط يسهل إعادة بناؤها. | |||
* المصانع المجردة والبانيات والنماذج الأولية يمكن استخدامها جميعها كمفردات. | |||
== الاستخدام في لغة جافا == |
مراجعة 04:41، 4 يناير 2019
نمط النموذج الأولي (prototype) هو نمط تصميم إنشائي يسمح لك بنسخ الكائنات الموجودة حاليًا دون جعل شيفرتك تعتمد على فئات تلك الكائنات.
المشكلة
لنقل أن لديك كائنًا تريد إنشاء نسخة طبق الأصل منه، كيف ستفعل ذلك؟ ستنشئ أولًا كائنًا جديدًا من نفس الفئة ثم ستنسخ كل القيم الموجودة في الكائن الأصلي إلى الكائن الجديد. لكن ليست كل الكائنات يمكن نسخها بتلك الطريقة إذ أن بعض حقول الكائن قد تكون خاصة وغير مرئية من خارج الكائن نفسه.
لديك مشكلة ثانية مع هذا المنظور المباشر، فلأن عليك معرفة فئة الكائن لإنشاء نسخة منه، فإن شيفرتك ستعتمد على تلك الفئة، وإن لم تقلقك تلك الاعتماديات الإضافية فهناك مشكلة أخرى، فإنك أحيانًا قد لا تعرف سوى الواجهة التي يتبعها ذلك الكائن ولا تعرف فئته الحقيقية، في حين أنك قد يكون لديك معامِل مثلًا يقبل أي كائنات تتبع واجهة ما.
الحل
يفوض نمط النموذج الأولي عملية الاستنساخ إلى الكائنات الحقيقية التي تُستنسخ، ويصرّح عن واجهة مشتركة لكل الكائنات التي تدعم الاستنساخ، تسمح لك تلك الواجهة باستنساخ كائن ما دون ربط شيفرتك إلى فئة ذلك الكائن، وتحتوي تلك الواجهة عادة على أسلوب clone
وحيد.
ويتشابه استخدام أسلوب clone
إلى حد كبير في كل الفئات، فهو ينشئ كائنًا من الفئة الحالية وينقل كل قيم الحقول من الكائن القديم إلى الجديد، بل يمكنك أيضًا نسخ الحقول الخاصة (private fields) بسبب أن أغلب لغات البرمجة تسمح للكائنات بالوصول إلى الحقول الخاصة للكائنات الأخرى التي تنتمي إلى نفس الفئة. ويسمى الكائن الذي يدعم الاستنساخ بالنموذج الأولي (prototype)، وستجد أن عملية الاستنساخ مفيدة عندما تحتوي الكائنات التي لديك على عشرات الحقول ومئات الإعدادات المحتملة، إذ سيكون الاستنساخ حينها بديلًا للفئات الفرعية.
وإليك كيف تستنسخ الكائنات حين تحتاجها: تنشئ مجموعة كائنات وتهيئ إعداداتها كما تشاء، ثم حين تحتاج كائنًا شبيهًا بأحد تلك الكائنات التي أنشأتها فإنك تستنسخ نموذجًا أوليًا (prototype) بدلًا من إنشاء كائن جديد من الصفر.
مثال حي
تُستخدم النماذج الأولية في المجالات الصناعية لإجراء اختبارات عديدة قبل البدء في الإنتاج بكميات كبيرة، لكن لا تساهم في أي منتج حقيقي، وإنما تقوم بدور مستترن وعليه فإن النماذج الأولية الصناعية لا تنسخ أنفسها حقًا وإنما تقوم مقال المراحل الأولية التجريبية للمنتج الفعلي، ولذا فإن أقرب مثال يوضح فكرة نمط النموذج الأولي سيكون انقسام الخلايا غير المباشر في الكائنات الحية، فبعده يكون لدينا زوجًا من خليتين متطابقتين تمامًا، وتكون الخلية الأصلية هنا بمثابة النموذج الأولي وتؤدي دورًا وظيفيًا يماثل دور الخلية المستنسَخة تمامًا في الكائن الحي.
البُنية
التطبيق الأساسي
- تصرح واجهة النموذج الأولي (prototype) عن أساليب الاستنساخ، وفي أغلب الحالات يكون أسلوب
clone
وحيد. - تستخدم فئة النموذج الأولي الحقيقي (Concrete prototype) أسلوب الاستنساخ، وإضافة إلى نسخ بيانات الكائن الأصلي إلى المستنسَخ، فقد يتولى هذا الأسلوب أيضًا بعض الحالات الشاذة لعملية الاستنساخ المتعلقة باستنساخ الكائنات المرتبطة ببعضها أو حل الاعتماديات التكرارية (recursive dependencies)، إلخ
- يمكن للعميل (Client) أن ينتج نسخة من أي كائن يتبع واجهة النموذج الأولي.
تطبيق سجل النموذج الأولي
- يوفر سجل النموذج الأولي (Prototype Registry) طريقة سهلة للوصول إلى النماذج الأولية المستخدمة بكثرة، فهو يخزن مجموعة من الكائنات المبنية مسبقًا والجاهزة للنسخ. وأسهل تسجيل للنموذ الأولي هو خريطة مزيج
name → prototype
، لكن بأي حال، إن كنت تريد معايير بحث أفضل من مجرد اسم بسيط فيمكنك بناء نسخة من السجل أفضل من ذلك وأثبَتْ.
مثال توضيحي
يسمح لك نمط النموذج الأولي في هذا المثال بإنتاج نسخ مطابقة من كائنات هندسية دون ربط الشيفرة إلى فئاتها، وتتبع كل فئات shape
في هذا المثال نفس الواجهة التي توفر أسلوب استنساخ بدورها، والفئة الفرعية يمكنها استدعاء أسلوب الاستنساخ من فئتها الأم قبل نسخ قيم الحقول الخاصة بها في الكائن الناتج.
// النموذج الأولي الأساسي.
abstract class Shape is
field X: int
field Y: int
field color: string
// مؤسس عادي.
constructor Shape() is
// ...
// مؤسس النموذج الأولي. يُبدأ كائن جديد بقيم من الكائن الموجود فعلًا.
constructor Shape(source: Shape) is
this()
this.X = source.X
this.Y = source.Y
this.color = source.color
// الفرعية shape تعيد عملية الاستنساخ إحدى فئات.
abstract method clone():Shape
// النموذج الأولي الحقيقي. تنشئ عملية الاستنساخ كائنًا جديدًا وتمرره إلى
// المؤسس. ويظل للمؤسس مرجعٌ إلى نسخة حديثة حتي ينتهي، لذا لا يكون لأي أحد
// وصول إلى نسخة غير مكتملة البناء من أجل الحفاظ على ثبات النتيجة.
class Rectangle extends Shape is
field width: int
field height: int
constructor Rectangle(source: Rectangle) is
// استدعاء المؤسس الأساسي مطلوب لنسخ الحقول الخاصة المعرَّفة في
// الفئة الأم.
super(source)
this.width = source.width
this.height = source.height
method clone():Shape is
return new Rectangle(this)
class Circle extends Shape is
field radius: int
constructor Circle(source: Circle) is
super(source)
this.radius = source.radius
method clone():Shape is
return new Circle(this)
// في مكان ما داخل شيفرة العميل
class Application is
field shapes: array of Shape
constructor Application() is
Circle circle = new Circle()
circle.width = 10
circle.height = 10
circle.radius = 20
shapes.add(circle)
Circle anotherCircle = circle.clone()
shapes.add(anotherCircle)
// على نسخة طبق الأصل من 'anotherCircle' يحتوي متغير
// 'circle' كائن.
Rectangle rectangle = new Rectangle()
rectangle.width = 10
rectangle.height = 20
shapes.add(rectangle)
method businessLogic() is
// تظهر أفضلية النموذج الأولي هنا إذ يسمح لك بإنتاج نسخة من الكائن
// دون معرفة أي شيء عن نوعه.
Array shapesCopy = new Array of Shapes.
// فمثلًا، لا نعرف عناصر مصفوفة الأشكال بالضبط، فكل ما نعرفه أنها أشكال
// 'clone' كلها. لكن بفضل تعدد الأشكال فإننا حين نستدعي أسلوب
// على شكل ما، فإن البرنامج يفحص فئته الحقيقية وينفِّذ أسلوب الاستنساخ
// المناسب والمعرَّف في تلك الفئة. هذا هو سبب حصولنا على نسخ صحيحة
// ومناسبة بدلًا من مجموعة كائنات أشكال بسيطة.
foreach (s in shapes) do
shapesCopy.add(s.clone())
// على نسخ طبق الأصل من `shapesCopy` تحتوي مصفوفة
// `shape` عناصر مصفوفة.
قابلية التطبيق
استخدم نمط النموذج الأولي حين تريد لشيفرتك ألا تعتمد على الفئات الحقيقية للكائنات التي تريد نسخها.
يحدث هذا كثيرًا حين تعمل شيفرتك مع كائنات ممررة إليك من شيفرة خارجية عبر واجهة ما، ولا تعرف الفئات الحقيقية لتلك الكائناتن ولا يمكنك الاعتماد عليها حتى لو أردت. فيوفر نمط النموذج الأولي ها هنا لشيفرة العميل واجهة عامة للعمل مع كل الكائنات التي تدعم الاستنساخ، وتلك الواجهة تجعل شيفرة العميل مستقلة عن الفئات الحقيقية للكائنات التي تستنسخها.
استخدم النمط حين تريد تقليل عدد الفئات الفرعية التي لا تختلف إلا في طريقة بدء كائناتها، فقد يكون أحدهم قد أنشأ تلك الفئات الفرعية ليتمكن من إنشاء كائنات بإعدادات محددة.
يسمح لك نمط النموذج الأولي باستخدام مجموعة كائنات مسبقة البناء ومُعدَّة بطرق مختلفة، يسمح لك باستخدامها كنماذج أولية. وبدلًا من تمثيل فئة فرعية تطابق بعض الإعدادات، فإن العميل يمكنه البحث ببساطة عن النموذج الأولي المناسب ويستنسخه.
كيفية الاستخدام
- أنشئ واجهة النموذج الأولي وصرّح عن أسلوب
clone
فيها، أو أضف الأسلوب إلى كل الفئات في الهرمية الحالية للفئات إن كانت لديك. - يجب أن تعرِّف فئة النموذج الأولي المنشئ البديل الذي يقبل كائنًا من تلك الفئة كوسيط، ويجب أن ينسخ المنشئ فيم كل الحقول المعرَّفة في تلك الفئة من الكائن المُمرَّر إلى النسخة المنشأة حديثًا. وإن كنت تغير فئة فرعية فيجب أن تستدعي المنشئ الأساسي (parent constructor) ليسمح للفئة الأساسية (superclass) بتولي عملية استنساخ حقولها الخاصة. أما إن كانت اللغة البرمجية التي تستخدمها لا تدعم التحميل الزائد (overloading)، فيمكنك أن تعرِّف أسلوبًا خاصًا لنسخ بيانات الكائن، ويمكنك استخدام المنشئ (constructor) كمكان مناسب لفعل ذلك بسبب أنه يسلم الكائن الناتج مباشرة بعد استدعاء معامِل
new
. - يتكون أسلوب الاستنساخ عادة من سطر واحد: تشغيل معامِل
new
مع النسخة النموذجية من المنشئ، لاحظ أنه يجب على كل فئة أن تتخطى أسلوب الاستنساخ وتستخدم اسم فئتها مع معاملnew
، وإلا فإن أسلوب الاستنساخ قد ينتج كائنًا من الفئة الأم. - أنشئ سجل نموذج أولي مركزي لتخزين فهرس بالنماذج الأولية التي تستخدم بكثرة.
يمكنك استخدام السجل كفئة مصنع جديد أو وضعه في فئة النموذج الأولي الأساسي مع أسلوب ثابت (static method) لجلب النموذج الأولي، يجب أن يبحث هذا الأسلوب عن النموذج الأولي بناءً على معيار البحث الذي تمرره شيفرة العميل إلى الأسلوب، وقد يكون معيار البحث نصًا بسيطًا أو مجموعة معامِلات بحث معقدة. وعند العثور على النموذج الأولي المناسب فإن السجل يستنسخه ويعيد النسخة إلى العميل.
وأخيرًا، استبدل الاستدعاءات المباشرة إلى منشئات الفئات الفرعيةباستدعاءات إلى أسلوب المصنع لسجل النموذج الأولي.
المزايا والعيوب
المزايا
- يمكنك استنساخ الكائنات دون ربطها بفئاتها الحقيقية.
- يمكنك التخلص من شيفرة البدء المتكررة لصالح نسخ نماذج أولية مبنية مسبقًا.
- يمكنك إنتاج كائنات معقدة بشكل أيسر.
- تحصل على بديل للاكتساب (Inheritance) عند التعامل مع تجهيزات الإعدادات المسبقة للكائنات المعقدة.
العيوب
- قد يكون من الصعب استنساخ الكائنات المعقدة التي لديها مراجع حِلَقية (circular references).
العلاقات مع الأنماط الأخرى
- تبدأ العديد من التصميمات مستخدمة أسلوب المصنع بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص عبر الفئات الفرعية، ثم تتطور إلى أنماط المصنع المجرد والنموذج الأولي والباني، إذ أنها أكثر مرونة لكنها معقدة أكثر في المقابل.
- تُبنى فئات المصنع المجرد عادة على مجموعة من أساليب المصنع، لكنك تستطيع استخدام نمط النموذج الأولي لتركيب الأساليب على تلك الفئات.
- التصاميم التي تستخدم نمط المركَّب والمزخرِف قد تستفيد من استخدام نمط النموذج الأولي، إذ يسمح باستخدام بُنى معقدة بدلًا من إعادة إنشائها من الصفر.
- لا يبنى نمط النموذج الأولي على الاكتساب (inheritance) لذا ليس له مساوئه، لكن من الناحية الأخرى فإن النموذج الأولي يتطلب عملية بدء (initialization) معقدة للكائن المستنسخ. في المقابل، أسلوب المصنع مبني على الاكتساب لكنه لا يتطلب خطوة بدء.
- أحيانًا قد يكون نمط النموذج الأول بديلًا بسيطًا لنمط التذكرة (Memento)، حين يكون الكائن، الحالة التي تريد تخزينها في السجل، بسيطة للغاية وليس لديها روابط إلى مصادر خارجية أو أن الروابط يسهل إعادة بناؤها.
- المصانع المجردة والبانيات والنماذج الأولية يمكن استخدامها جميعها كمفردات.