نمط المصنع المجرد

من موسوعة حسوب
مراجعة 04:53، 29 ديسمبر 2018 بواسطة أسامه-دمراني (نقاش | مساهمات) (إدخال 2.1 محتوى)

نمط المصنع المجرَّد هو نمط تصميم إنشائي يسمح لك بإنتاج عائلات من الكائنات المرتبطة ببعضها دون تحديد فئاتهم الحقيقية. (انظر ش.1)

المشكلة

تخيل أنك تنشئ محاكيًا لمتجر أثاث، وستتكون شيفرتك من فئات تمثل ما يلي:

  1. عائلة من المنتجات المرتبطة ببعضها، لنقل Chair + Sofa + CoffeeTable.
  2. عدة متغيرات من تلك العائلة، فمثلًا ستكون منتجات Chair + Sofa + CoffeeTable متاحة في المتغيرات التالية: IKEA، VictorianStyle، ArtDeco. (انظر ش.2).

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

الحل

أول ما يقترحه نمط المصنع المجرد هو أن تصرح بوضوح عن واجهات لكل نوع من المنتجات في عائلة المنتجات (مثل الكرسي Chair، أو الأريكة Sofa، أو منضدة القهوة Coffee table)، ثم يمكنك بعدها أن تجعل كل متغيرات المنتجات تتبع تلك الواجهات. فمثلًا تستخدم كل متغيرات الكراسي واجهة Chair، وكل متغيرات مناضد القهوة تستخدم واجهة CoffeeTable، وهكذا. (انظر ش.4)

والخطوة التالية هي التصريح عن واجهة AbstractFactory مع قائمة من أساليب الإنشاء لكل المنتجات الموجودة داخل عائلة المنتجات (مثلًا createChair، و createSofa، وcreateCoffeeTable)، ويجب أن تعيد تلك الأساليب أنواعًا مجردة من المنتجات ممثلة بالواجهات التي استخرجناها من قبل: Chair و Sofa و CoffeeTable، وهكذا. (انظر ش.5). ولكل متغير من عائلة المنتجات ننشئ فئة مصنع منفصلة بناءً على واجهة المصنع المجرد AbstractFactory، والمصنع هو فئة تعيد منتجات من نوع بعينه، ففئة مصنع أيكيا مثلًا IKEAFactory لا تنشئ إلا كائنات IKEAChair و IKEASofa و IKEACoffeeTable.

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

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

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

البُنية

  1. تصرح المنتجات المجردة عن واجهات لمجموعة من المنتجات المستقلة التي ترتبط ببعضها وتكوّن عائلة منتجات. (انظر ش.6)
  2. المنتجات الحقيقية (Concrete Products) هي استخدامات مختلفة للمنتجات المجردة مجموعةً مع بعضها حسب الشكل (Variant)، ويجب أن يُستخدم كل منتج مجرد في كل الأنواع المعطاة (فيكتوري، حديث، ... إلخ.)
  3. تصرح واجهة المصنع المجرد (Abstract factory) عن مجموعة من الأساليب لإنشاء كل منتج من المنتجات المجردة.
  4. تستخدم المصانع الحقيقية (Concrete Factories) أساليب إنشاء من المصنع المجرد، ويطابق كل مصنع حقيقي نوعًا من المنتجات وينشئ الأشكال المختلفة (variants) لذلك المنتج فقط.
  5. رغم أن كل المصانع الحقيقية تمثّل منتجات حقيقية إلا أن توقيعات أساليب إنشائها يجب أن تعيد المنتجات المجردة التي تطابقها، وذلك من أجل ألا تُقرن شيفرة العميل التي تستخدم مصنعًا إلى نوع محدد من أنواع منتج تحصل عليه من ذلك المصنع. ويستطيع العميل أن يعمل مع أي نوع من المصانع / المنتجات الحقيقية طالما أنه يتواصل مع عناصرها من خلال واجهات مجردة.

مثال توضيحي

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

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

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

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

// تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة.
// يطلق على هذه المنتجات اسم "عائلة" وترتبط مع بعضها بعامل مشترك موجود فيها جميعًا.
// منتجات العائلة الواحدة قادرة على التعاون فيما بينها.
// عائلة المنتجات قد تحتوي بضعة أنواع، لكن منتجات النوع الواحد لا تتوافق مع منتجات نوع آخر.
interface GUIFactory is
    method createButton():Button
    method createCheckbox():Checkbox

// تنتج المصانع الحقيقية عائلة منتجات تنتمي إلى نوع واحد،ويضمن المصنع أن المنتجات متوافقة.
// توقيعات أساليب المصنع الحقيقي تعيد منتجًا مجردً بينما يُمثَّل منتج حقيقي داخل الأسلوب.
class WinFactory implements GUIFactory is
    method createButton():Button is
        return new WinButton()
    method createCheckbox():Checkbox is
        return new WinCheckbox()

// كل مصنع حقيقي له نوع متوافق معه من المنتجات.
class MacFactory implements GUIFactory is
    method createButton():Button is
        return new MacButton()
    method createCheckbox():Checkbox is
        return new MacCheckbox()


// (base interface) يجب أن يكون لكل منتج مستقل من عائلة المنتجات واجهةً أساسية.
// ويجب أن تستخدم جميع صور المنتج هذه الواجهة.
interface Button is
    method paint()

// Concrete products are created by corresponding concrete
// factories.
// تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المتوافقة معها.
class WinButton implements Button is
    method paint() is
        // أخرج زرًا لنظام ويندوز.

class MacButton implements Button is
    method paint() is
        // أخرج زرًا لنظام ماك.

// إليك الواجهة الأساسية لمنتج آخر، يمكن لجميع المنتجات أن تتفاعل مع بعضها، لكن التفاعلات
// الصحيحةغير ممكنة إلا بين منتجات نفس النوع الحقيقي.
interface Checkbox is
    method paint()

class WinCheckbox implements Checkbox is
    method paint() is
        // أخرج صندوق اختيار بأسلوب ويندوز.

class MacCheckbox implements Checkbox is
    method paint() is
        // أخرج صندوق اختيار بأسلوب نظام ماك.


// Checkbox و Button و GUIFactory تعمل شيفرة العميل مع المصانع والمنتجات من خلال أنواع مجردة فقط هي.
// يسمح هذا لك بتمرير أي فئة فرعية لمصنع أو منتج إلى شيفرة العميل دون تعطيلها.
class Application is
    private field button: Button
    constructor Application(factory: GUIFactory) is
        this.factory = factory
    method createUI() is
        this.button = factory.createButton()
    method paint() is
        button.paint()



// يختار التطبيق نوع المصنع بناءً على الإعدادات الحالية أو إعدادات البيئة، وينشئه في
// -وقت التشغيل -غالبًا في مرحلة البدء.
class ApplicationConfigurator is
    method main() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            factory = new WinFactory()
        else if (config.OS == "Web") then
            factory = new MacFactory()
        else
            throw new Exception("Error! Unknown operating system.")

        Application app = new Application(factory)

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

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

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

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

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

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

  1. ارسم مصفوفة من أصناف المنتجات المختلفة في مقابل الأشكال المختلفة من تلك المنتجات.
  2. صرِّح عن واجهات منتجات مجردة لكل أصناف المنتجات، ثم اجعل كل فئات المنتج الحقيقي تستخدم تلك الواجهات.
  3. صرِّح عن واجهة المصنع المجرد مع مجموعة من أساليب الإنشاء لكل المنتجات المجردة.
  4. استخدم مجموعة من فئات المصنع الحقيقي، واحدة لكل شكل من أشكال المنتج.
  5. أنشئ شيفرة بدء المصنع في مكان ما في التطبيق، يجب أن تمثّل هذه الشيفرة إحدى فئات المصنع الحقيقي بناءً على إعدادات التطبيق أو البيئة الحالية. ينبغي أن تمرِّر كائن المصنع ذاك إلى كل الفئات التي تنشئ منتجات.
  6. ابحث في الشيفرة عن كل الاستدعاءات المباشرة لمنشئات المنتجات (product constructors) واستبدلهم باستدعاءات إلى أسلوب الإنشاء المناسب في كائن المصنع.

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

المزايا

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

العيوب

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

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

  • تستخدم تصميمات كثيرة أسلوب المصنع (Factory Method) بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص عبر الفئات الفرعية، ثم تنتقل إلى أسلوب المصنع المجرد أو النموذج الأولي (prototype) أو الباني (Builder)، حيث أنهم أكثر مرونة لكن أكثر تعقيدًا في المقابل.
  • يركز نمط الباني (Builder) على إنشاء كائنات معقدة خطوة بخطوة، أما المصنع المجرد فيتخصص في إنشاء عائلات من المنتجات المتعلقة ببعضها، كذلك يعيد المنتج فورًا في حين أن الباني يسمح لك بإجراء بعض خطوات الإنشاء الإضافية قبل الحصول على المنتج.
  • تُبنى فئات المصنع المجرد في الغالب على مجموعة من أساليب المصنع لكن تستطيع استخدام نمط النموذج الأولي لتشكيل الأساليب في تلك الفئات.
  • قد يُستخدم المصنع المجرد كبديل لنمط الواجهة حين تريد إخفاء طريقة إنشاء كائنات النظام الفرعي عن شيفرة العميل.
  • من المفيد استخدام المصنع المجرد مع نمط الجسر في الحالة التي لا تعمل فيها بعض المجرَّدات (abstractions) التي يعرِّفها نمط الجسر إلا مع استخدامات محددة، ففي تلك الحالة يمكن للمصنع المجرد أن يغلّف هذه العلاقات ويخفي التعقيد من شيفرة العميل.
  • يمكن استخدام المصانع المجردة والبانيات (Builders) والنماذج الأولية كمفردات.

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

المستوى: متوسط

الانتشار: واسع الانتشار

أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة جافا إذ يُستخدم في مكتبات وأُطر عمل عديدة لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية. إليك بعض الأمثلة من مكتبات جافا:

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

مثال: إنتاج عائلات من عناصر واجهة متعددة المنصات

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

الأزرار: هرمية أول منتج

 buttons/Button.java
package refactoring_guru.abstract_factory.example.buttons;

/**
 * (Button/Checkbox) يفترض المصنع المجرد أن لديك عدة عائلات من المنتجات مقسمة إلى هرميات فئوية مستقلة
 * وكل المنتجات من نفس العائلة لها نفس الواجهة.
 *
 * هذه هي الواجهة المشتركة لعائلة الأزرار.
 */
public interface Button {
    void paint();
}
 buttons/MacOSButton.java
package refactoring_guru.abstract_factory.example.buttons;

/**
 * كل عائلات المنتجات لها نفس الأشكال: ويندوز/ماك.
 *
 * وهذا شكل الزر في نظام ماك.
 */
public class MacOSButton implements Button {

    @Override
    public void paint() {
        System.out.println("You have created MacOSButton.");
    }
}
 buttons/WindowsButton.java
package refactoring_guru.abstract_factory.example.buttons;

/**
 * كل عائلات المنتجات لها نفس الأشكال: ويندوز/ماك.
 *
 * هذا شكل آخر للزر.
 */
public class WindowsButton implements Button {

    @Override
    public void paint() {
        System.out.println("You have created WindowsButton.");
    }
}

صناديق الاختيار: هرمية ثاني منتج

 checkboxes/Checkbox.java
package refactoring_guru.abstract_factory.example.checkboxes;

/**
 * صناديق الاختيار هي ثاني عائلة منتجات، ولها نفس الأشكال كالأزرار.
 */
public interface Checkbox {
    void paint();
}
 checkboxes/MacOSCheckbox.java
package refactoring_guru.abstract_factory.example.checkboxes;

/**
 * كل عائلات المنتجات لها نفس الأشكال: ويندوز/ماك.
 *
 * هذا أحد أشكال صندوق الاختيار.
 */
public class MacOSCheckbox implements Checkbox {

    @Override
    public void paint() {
        System.out.println("You have created MacOSCheckbox.");
    }
}
 checkboxes/WindowsCheckbox.java
package refactoring_guru.abstract_factory.example.checkboxes;

/**
 * كل عائلات المنتجات لها نفس الأشكال: ويندوز/ماك.
 *
 * هذا شكل آخر لصندوق الاختيار.
 */
public class WindowsCheckbox implements Checkbox {

    @Override
    public void paint() {
        System.out.println("You have created WindowsCheckbox.");
    }
}

المصانع

 factories/GUIFactory.java: المصنع المجرد
package refactoring_guru.abstract_factory.example.factories;

import refactoring_guru.abstract_factory.example.buttons.Button;
import refactoring_guru.abstract_factory.example.checkboxes.Checkbox;

/**
 * يعرف المصنع المجرد جميع الأنواع المجردة للمنتج.
 */
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}
factories/MacOSFactory.java: المصنع الحقيقي (نظام ماك)
package refactoring_guru.abstract_factory.example.factories;

import refactoring_guru.abstract_factory.example.buttons.Button;
import refactoring_guru.abstract_factory.example.buttons.MacOSButton;
import refactoring_guru.abstract_factory.example.checkboxes.Checkbox;
import refactoring_guru.abstract_factory.example.checkboxes.MacOSCheckbox;

/**
 * يوسّع كل مصنع حقيقي مصنعًا أساسيًا ويكون مسؤولًا عن إنشاء
 * منتجات من صنف واحد.
 */
public class MacOSFactory implements GUIFactory {

    @Override
    public Button createButton() {
        return new MacOSButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}
factories/WindowsFactory.java: المصنع الحقيقي (ويندوز)
package refactoring_guru.abstract_factory.example.factories;

import refactoring_guru.abstract_factory.example.buttons.Button;
import refactoring_guru.abstract_factory.example.buttons.WindowsButton;
import refactoring_guru.abstract_factory.example.checkboxes.Checkbox;
import refactoring_guru.abstract_factory.example.checkboxes.WindowsCheckbox;

/**
 * كل مصنع حقيقي يوسع مصنعًا أساسيًا ويكون مسؤولًا عن إنشاء منتجات
 * من صنف واحد.
 */
public class WindowsFactory implements GUIFactory {

    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

التطبيق (App)

 app/Application.java: شيفرة العميل
package refactoring_guru.abstract_factory.example.app;

import refactoring_guru.abstract_factory.example.buttons.Button;
import refactoring_guru.abstract_factory.example.checkboxes.Checkbox;
import refactoring_guru.abstract_factory.example.factories.GUIFactory;

/**
 * مستخدمو المصنع لا يهمهم أي مصنع حقيقي يستخدمونه طالما أنهم يعملون مع مصانع
 * ومنتجات من خلال واجهات مجردة.
 */
public class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.paint();
        checkbox.paint();
    }
}
 Demo.java: إعدادات التطبيق
package refactoring_guru.abstract_factory.example;

import refactoring_guru.abstract_factory.example.app.Application;
import refactoring_guru.abstract_factory.example.factories.GUIFactory;
import refactoring_guru.abstract_factory.example.factories.MacOSFactory;
import refactoring_guru.abstract_factory.example.factories.WindowsFactory;

/**
 * (Demo) يجتمع كل شيء هنا في فئة العرض.
 */
public class Demo {

    /**
     * يختار التطبيق نوع المصنع وينشئه في وقت التشغيل (عادة في مرحلة البدء) بناءً
     * على الإعدادات أو البيئة الحالية.
     */
    private static Application configureApplication() {
        Application app;
        GUIFactory factory;
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.contains("mac")) {
            factory = new MacOSFactory();
            app = new Application(factory);
        } else {
            factory = new WindowsFactory();
            app = new Application(factory);
        }
        return app;
    }

    public static void main(String[] args) {
        Application app = configureApplication();
        app.paint();
    }
}
 OutputDemo.txt: نتائج التنفيذ
You create WindowsButton.
You created WindowsCheckbox.

الاستخدام في لغة #C

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

انظر أيضًا

مصادر