نمط النموذج الأولي

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث

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

المشكلة

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

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

الحل

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

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

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

مثال حي

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

البُنية

التطبيق الأساسي

  1. تصرح واجهة النموذج الأولي (prototype) عن أساليب الاستنساخ، وفي أغلب الحالات يكون أسلوب clone وحيد.
  2. تستخدم فئة النموذج الأولي الحقيقي (Concrete prototype) أسلوب الاستنساخ، وإضافة إلى نسخ بيانات الكائن الأصلي إلى المستنسَخ، فقد يتولى هذا الأسلوب أيضًا بعض الحالات الشاذة لعملية الاستنساخ المتعلقة باستنساخ الكائنات المرتبطة ببعضها أو حل الاعتماديات التكرارية (recursive dependencies)، إلخ
  3. يمكن للعميل (Client) أن ينتج نسخة من أي كائن يتبع واجهة النموذج الأولي.

تطبيق سجل النموذج الأولي

  1. يوفر سجل النموذج الأولي (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` عناصر مصفوفة.

قابلية التطبيق

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

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

استخدم النمط حين تريد تقليل عدد الفئات الفرعية التي لا تختلف إلا في طريقة بدء كائناتها،