أسلوب المصنع

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

أسلوب المصنع هو نمط تصميم إنشائي (creational) يوفر واجهة لإنشاء الكائنات (objects) داخل فئات رئيسية (superclasses) لكنها تسمح في نفس الوقت للفئات الثانوية (subclasses) بتغيير نوع تلك الكائنات التي سيتم إنشاؤها.

المشكلة

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

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

الحل

يقترح أسلوب المصنع هنا أن تستبدل الاستدعاءات المباشرة لإنشاء الكائنات -من خلال معامِل new- باستدعاءات لأسلوب factory الخاص، ما سيحدث هو أن الكائنات سيتم إنشاؤها بمعامل new كما تقدم لكنها ستُستَدعى من داخل أسلوب المصنع. ويشار إلى تلك الكائنات التي تُعاد (returned) بأسلوب المصنع باسم المنتجات (products).

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

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

فمثلًا، يجب أن تستخدم الفئتان Truck و Ship واجهة Transport التي تصرح عن أسلوب اسمه deliver، وتنفذ كل فئة هذا الأسلوب بشكل مختلف، فالشاحنات تسلم حمولتها على الأرض، والسفن تسلمها عبر البحار، ويعيد أسلوب المصنع كائنات الشاحنات في فئة RoadLogistics، بينما يعيد سفنًا في فئة SeaLogistics.

ولا ترى الشيفرة البرمجية التي تستخدم أسلوب المصنع -قد يطلق عليها عادة شيفرة العميل (client code)-، لا ترى أي فرق بين المنتجات الحقيقية التي أعيدت من خلال الفئات الفرعية، ويعامل العميل جميع المنتجات كواجهة Transport افتراضية. ويعرف العميل أن كل كائنات النقل يجب أن يكون لديها أسلوب deliver، لكن لا يهم عنده كيفية عملها بالضبط.

البُنية

ضع الصورة

  1. يصرّح المنتج بالواجهة التي ستكون مشتركة لكل الكائنات التي يمكن إنتاجها بواسطة المنشئ (creator) وفئاته الفرعية.
  2. المنتجات الحقيقية (concrete products) هي الاستخدامات المختلفة لواجهة المنتج.
  3. تصرّح فئة المنشئ عن أسلوب المصنع الذي يعيد كائنات المنتج الجديد، من المهم أن نوع الإعادة من هذا الأسلوب يطابق واجهة المنتج. تستطيع تصريح أسلوب المصنع كأسلوب افتراضي لإجبار كل الفئات الفرعية على استخدام نسخها الخاص من الأسلوب، وكطريقة بديلة فإن أسلوب المصنع الأساسي يمكنه إعادة بعض الأنواع الافتراضية للمنتجات. لاحظ أن الوظيفة الأساسية للمنشئ (creator) ليست إنشاء المنتجات رغم التسمية التي توحي بهذا، فإن فئة المنشئ عادة يكون لها منطق تجاري متعلق بالمنتجات، ويساعد أسلوب المصنع في فصل هذا المنطق عن فئات المنتجات الحقيقية. إليك مثالًا يوضح الأمر، تستطيع شركات البرمجيات الكبيرة توفير أقسام لتدريب المبرمجين، لكن ذلك لا يعني أن الوظيفة الأساسية للشركة ككل هي إنتاج مبرمجين، بل كتابة البرامج هي وظيفتها.
  4. تتخطى منشئات المنتجات الحقيقية (Concrete Creators) أسلوب المصنع الأساسي لذا تعيد نوعًا مختلفًا من المنتجات، لاحظ أن أسلوب المصنع قد لا ينشئ حالات جديدة دومًا، فقد يعيد أحيانًا كائنات موجودة من مصادر مختلفة كالذاكرة المؤقتة (Cache memory) أو حوض الكائنات (object pool)، أو غيرها.

مثال توضيحي

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

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

ولكي يعمل هذا النمط، يجب أن تعمل فئة الصندوق الحواري الأساسية مع أزرار افتراضية: كفئة أساسية أو واجهة تتبعها كل أزرار المنتجات الحقيقية، وبهذه الطريقة تظل شيفرة الصندوق الحواري عاملة بغض النظر عن نوع الأزرار التي تعمل معها.

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

// The creator class declares the factory method that must
// return an object of a product class. The creator's subclasses
// usually provide the implementation of this method.
class Dialog is
    // The creator may also provide some default implementation
    // of the factory method.
    abstract method createButton()

    // Note that, despite its name, the creator's primary
    // responsibility isn't creating products. It usually
    // contains some core business logic that relies on product
    // objects returned by the factory method. Subclasses can
    // indirectly change that business logic by overriding the
    // factory method and returning a different type of product
    // from it.
    method renderWindow() is
        // Call the factory method to create a product object.
        Button okButton = createButton()
        // Now use the product.
        okButton.onClick(closeDialog)
        okButton.render()


// Concrete creators override the factory method to change the
// resulting product's type.
class WindowsDialog extends Dialog is
    method createButton() is
        return new WindowsButton()

class WebDialog extends Dialog is
    method createButton() is
        return new HTMLButton()


// The product interface declares the operations that all
// concrete products must implement.
interface Button is
    method render()
    method onClick(f)

// Concrete products provide various implementations of the
// product interface.
class WindowsButton implements Button is
    method render(a, b) is
        // Render a button in Windows style.
    method onClick(f) is
        // Bind a native OS click event.

class HTMLButton implements Button is
    method render(a, b) is
        // Return an HTML representation of a button.
    method onClick(f) is
        // Bind a web browser click event.


class Application is
    field dialog: Dialog

    // The application picks a creator's type depending on the
    // current configuration or environment settings.
    method initialize() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            dialog = new WindowsDialog()
        else if (config.OS == "Web") then
            dialog = new WebDialog()
        else
            throw new Exception("Error! Unknown operating system.")

    // The client code works with an instance of a concrete
    // creator, albeit through its base interface. As long as
    // the client keeps working with the creator via the base
    // interface, you can pass it any creator's subclass.
    method main() is
        dialog.initialize()
        dialog.render()

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

استخدم أسلوب المصنع عند عدم معرفة أنواع الكائنات التي ستتعامل شيفرتك معها، وكذلك عند عدم معرفة اعتمادياتها.

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

استخدم أسلوب املصنع حين تريد تزويد مستخدمي مكتبتك أو إطار عملك بطريقة لزيادة عناصره الداخلية.

أسهل طريقة لتوسيع السلوك الافتراضي لمكتبة أو إطار عمل هو الاكتساب أو الوراثة (inheritance)، لكن أنّى لإطار العمل معرفة أن فئتك الفرعية يجب أن تُستخدم بدلًا من العنصر القياسي؟

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

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

ولفعل ذلك تنشئ فئة فرعية لتكن UIWithRoundButtons من فئة إطار العمل الأساسية وتتخطى أسلوب createButton الخاص بها، ويمكنك أن تجعل فئتك الفرعية تعيد كائنات RoundButton رغم أن هذا الأسلوب يعيد كائنات Button في فئته الأساسية. والآن لم يتبق سوى أن تستخدم فئة UIWithRoundButtons بدلًا من UIFramework.

استخدم أسلوب المصنع عند توفير موارد النظام بإعادة استخدام الكائنات الموجودة بدلًا من إعادة إنشائهم في كل مرة.

قد تحدث تلك الحالة حين تتعامل مع كائنات كبيرة وتستهلك الموارد مثل اتصالات قواعد البيانات أو أنظمة الملفات أو موارد الشبكات. إليك ما يجب فعله لإعادة استخدام كائن موجود فعلًا:

  1. أولًا عليك توفير بعض المساحة لمتابعة كل الكائنات المنشأة.
  2. عندما يطلب شخص أحد الكائنات فإن البرنامج يبحث عن كائن متاح في حوض الكائنات (object pool)، ثم يعيده إلى شيفرة العميل.
  3. إن لم تكن هناك كائنات متاحة فيجب أن ينشئ البرنامج واحدًا ويضيفه إلى الحوض..

هذه شيفرة طويلة، ويجب أن تجمعها كلها في مكان واحد لكي لا تلوث البرنامج بشيفرة مكررة.

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

كيفية الاستخدام

  1. تأكد أن تتبع كل المنتجات نفس الواجهة، ويجب أن تصرح هذا الواجهة عن الأساليب التي تناسب كل منتج.
  2. أضف أسلوب مصنع فارغ داخل فئة المنشئ (creator)، يجب أن يطابق نوع الإعادة واجهة المنتج المشتركة.
  3. ابحث عن كل المراجع إلى منشئات المنتج (product constructors) داخل شيفرة المنشئ (creator)، واستبدل كل واحدة فيهم باستدعاءات إلى أسلوب المصنع في نفس الوقت الذي تستخرج فيه شيفرة إنشاء المنتج (creation code) إلى أسلوب المصنع، قد تحتاج إلى إضافة معامل مؤقت لأسلوب المصنع من أجل التحكم في نوع المنتج المُعاد. وقد تبدو شيفرة أسلوب المصنع في هذه المرحلة قبيحة، وقد يكون فيها معامل switch كبير يلتقط أي فئة منتج ليمثّلها، لكننا سنحل هذا قريبًا.
  4. أنشئ سلسلة من فئات creator الفرعية لكل نوع من المنتجات الموجودة في أسلوب المصنع، وتخطى أسلوب المصنع في الفئات الفرعية واستخرج الأجزاء المناسبة من شيفرة البناء (construction code) من الأسلوب الأساسي.
  5. إن كانت أنواع المنتجات كثيرة وليس من المنطقي إنشاء فئات فرعية لهم جميعًا فيمكنك إعادة استخدام معامل التحكم (control parameter) من الفة الأساسية داخل الفئات الفرعية. فمثلًا، تخيل أن لديك الهرمية التالية من الفئات: فئة Mail الأساسية مع بضعة فئات فرعية: AirMail و GroundMail، فتكون فئات واجهة Transport هي Plane و Truck و Train. وبينما تستخدم AirMail كائنات من Plane، فإن GroundMail ستعمل مع كائنات Truck و Train كليهما. يمكنك إنشاء فئات فرعية جديدة (TrainMail مثلًا) لمعالجة كلا الحالتين، لكن هناك خيار آخر، يمكن لشيفرة العميل أن تمرر وسيطًا (argument) لأسلوب المصنع الخاص بفئة GroundMail لاختيار أي المنتجات التي يريد استقبالها.
  6. إن أصبح أسلوب المصنع الأساسي فارغًا بعد كل الاستخراجات فيمكنك تحويله ليكون نظريًا (abstract)، أما إن تبقى شيء ما فيمكنك جعله السلوك الافتراضي للأسلوب.

المزايا والعيوب

المزايا

تتجنب الربط الوثيق بين منتجات المنشئ (creator products) والمنتجات الحقيقية (Concrete Products).

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

مبدأ المفتوح/المغلق. يمكنك إدخال أنواع جديدة من المنتجات داخل البرنامج دون تعطيل شيفرة العميل الحالية.

العيوب

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

العلاقات مع الأنماط الأخرى

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

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

يمكنك استخدام أسلوب المصنع إلى جانب المكرِّر لتسمح لمجموعات من الفئات الفرعية بإعادة أنواع مختلفة من المكرٍّرات تتوافق مع المجموعات.

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

أسلوب المصنع هو استثناء من أسلوب القالب (Template Method)، لكنه قد يخدم كمرحلة داخل أسلوب كبير من نوع القالب.

الاستخدام في لغة جافا

  • التعقيد: مبتدئ
  • الانتشار: