نمط المصنع المجرَّد هو نمط تصميم إنشائي يسمح لك بإنتاج عائلات من الكائنات المرتبطة ببعضها دون تحديد فئاتهم الحقيقية. (انظر ش.1)
المشكلة
تخيل أنك تنشئ محاكيًا لمتجر أثاث، وستتكون شيفرتك من فئات تمثل ما يلي:
عائلة من المنتجات المرتبطة ببعضها، لنقل Chair + Sofa + CoffeeTable.
ش.2 عائلات المنتجات وأشكالها المختلفةعدة متغيرات من تلك العائلة، فمثلًا ستكون منتجات Chair + Sofa + CoffeeTable متاحة في المتغيرات التالية: IKEA، VictorianStyle، ArtDeco. (انظر ش.2).
ش.3 أريكة IKEA لا تتوافق مع الكراسي التي على الطراز الفيكتوري
وستحتاج إلى طريقة لإنشاء كائنات أثاث منفردة لتتماشى مع عناصر أخرى من نفس عائلتها، ذلك أن العملاء يغضبون إن استلموا أثاثًا غير متماثل (انظر ش.3). أيضًا ينبغي ألا تغير الشيفرة الموجودة حين تضيف منتجات جديدة أو عائلات منتجات إلى البرنامج، فشركات الأثاث تجدد فهارسها كثيرًا ولا تريد أنت أن تغير شيفرتك في كل مرة تتغير الفهارس!
الحل
أول ما يقترحه نمط المصنع المجرد هو أن تصرح بوضوح عن واجهات لكل نوع من المنتجات في عائلة المنتجات (مثل الكرسي Chair، أو الأريكة Sofa، أو منضدة القهوة Coffee table)، ثم يمكنك بعدها أن تجعل كل متغيرات المنتجات تتبع تلك الواجهات. فمثلًا تستخدم كل متغيرات الكراسي واجهة Chair، وكل متغيرات مناضد القهوة تستخدم واجهة CoffeeTable، وهكذا. (انظر ش.4)dpaf.solution1.png
ش.4 يجب أن تنتقل كل أشكال الكائن الواحد إلى هرمية فئة واحدة
والخطوة التالية هي التصريح عن واجهة AbstractFactory مع قائمة من أساليب الإنشاء لكل المنتجات الموجودة داخل عائلة المنتجات (مثلًا createChair، و createSofa، وcreateCoffeeTable)، ويجب أن تعيد تلك الأساليب أنواعًا مجردة من المنتجات ممثلة بالواجهات التي استخرجناها من قبل: Chair و Sofa و CoffeeTable، وهكذا. (انظر ش.5). ولكل متغير من عائلة المنتجات ننشئ فئة مصنع منفصلة بناءً على واجهة المصنع المجرد AbstractFactory، والمصنع هو فئة تعيد منتجات من نوع بعينه، ففئة مصنع أيكيا مثلًا IKEAFactory لا تنشئ إلا كائنات IKEAChair و IKEASofa و IKEACoffeeTable.
يتوافق كل مصنع حقيقي مع شكل معين من أشكال المنتج
ويجب أن تعمل شيفرة العميل مع كلًّا من المصانع والمنتجات من خلال واجهاتهم المجردة، ذلك يسمح لك بتغيير نوع المصنع الذي تمرره إلى شيفرة العميل وكذلك متغير المنتج الذي تستقبله تلك الشيفرة دون تعطيل شيفرة العميل الفعلية. (انظر ش.6)
وحين يريد العميل مصنعًا لإنتاج كرسي فليس عليه أن يعرف فئة المصنع ولا يهمه أي نوع من الكراسي ستأتي به، فيجب أن يعامل العميل كل الكراسي بأسلوب واحد سواء كان الكرسي IKEA أو من الشكل الفيكتوري، من خلال استخدام واجهة Chair المجردة، وطبقًا لهذا المنظور فإن الشيء الوحيد الذي يعرفه العميل عن الكرسي هو أنه يستخدم أسلوب sit بشكل ما، وأنه سيماثل نوع الأريكة أو منضدة القهوة التي ينتجها نفس كائن المصنع، بغض النظر عن نوع الكرسي الذي سيُعاد.
وإن كان العميل لا يتعرض إلا للواجهات المجردة، فمن ينشئ كائنات المصنع الحقيقية؟ عادة ما ينش التطبيق كائن مصنع حقيقي في مرحلة البدء، لكن يجب أن يختار التطبيق نوع المصنع قبل ذلك مباشرة، اعتمادًا على إعدادات التهيئة أو إعدادات البيئة.
البُنية
ليس على العميل أن يهتم بفئة المصنع الحقيقية التي يعمل معها.تصرح المنتجات المجردة عن واجهات لمجموعة من المنتجات المستقلة التي ترتبط ببعضها وتكوّن عائلة منتجات. (انظر ش.7)
المنتجات الحقيقية (Concrete Products) هي استخدامات مختلفة للمنتجات المجردة مجموعةً مع بعضها حسب الشكل (Variant)، ويجب أن يُستخدم كل منتج مجرد في كل الأنواع المعطاة (فيكتوري، حديث، ... إلخ.)
تصرح واجهة المصنع المجرد (Abstract factory) عن مجموعة من الأساليب لإنشاء كل منتج من المنتجات المجردة.
تستخدم المصانع الحقيقية (Concrete Factories) أساليب إنشاء من المصنع المجرد، ويطابق كل مصنع حقيقي نوعًا من المنتجات وينشئ الأشكال المختلفة (variants) لذلك المنتج فقط.
رغم أن كل المصانع الحقيقية تمثّل منتجات حقيقية إلا أن توقيعات أساليب إنشائها يجب أن تعيد المنتجات المجردة التي تطابقها، وذلك من أجل ألا تُقرن شيفرة العميل التي تستخدم مصنعًا إلى نوع محدد من أنواع منتج تحصل عليه من ذلك المصنع. ويستطيع العميل أن يعمل مع أي نوع من المصانع / المنتجات الحقيقية طالما أنه يتواصل مع عناصرها من خلال واجهات مجردة.
مثال توضيحي
يوضح هذا المثال كيف يمكن استخدام نمط المصنع المجرد لإنشاء عناصر واجهة تصلح للمنصات المختلفة دون ربط شيفرة العميل بالفئات الحقيقية للواجهة، مع إبقاء جميع العناصر المنشأة متناسقة مع نظام التشغيل الموجهة إليه.
ينبغي أن تتصرف نفس عناصر الواجهة في تطبيق موجه لمنصات مختلفة بنفس الأسلوب وإن ظهرت بشكل مختلف قليلًا على نظم التشغيل المختلفة، وهي مهمتك أن تتأكد أن عناصر الواجهة تطابق نسق النظام الحالي، فلا يُظهر برنامجك أزرار تحكم لنظام ماك في حين أنه يعمل على ويندوز. وتصرح واجهة المصنع المجرد عن مجموعة أساليب إنشائية يمكن لشيفرة العميل أن تستخدمها لإنتاج أنواع مختلفة من عناصر الواجهة، وتتسق المصانع الحقيقية مع أنظمة التشغيل لتنشئ عناصر واجهة تماثل أسلوب الواجهة في تلك الأنظمة. (انظر ش.8)
ويسير الأمر كما يلي، حين يبدأ البرنامج فإنه يتفقد نوع نظام التشغيل الحالي لإنشاء كائن مصنع من فئة تطابق هذا النظام، ثم تستخدم بقية الشيفرة هذا المصنع لإنشاء عناصر الواجهة، ذلك يحول دون إنشاء عناصر خاطئة لا تناسب النظام الحالي. وهكذا لا تعتمد شيفرة العميل على الفئات الحقيقية للمصانع وعناصر الواجهة طالما أنها تعمل مع هذه الكائنات من خلال واجهاتها المجردة، وذلك يسمح أيضًا لشيفرة العميل أن تدعم المصانع الأخرى أو عناصر الواجهة التي قد تضيفها في المستقبل.
وكنتيجة لهذا فلا تحتاج إلى تعديل شيفرة العميل في كل مرة تضيف نوعًا جديدًا من عناصر الواجهة إلى تطبيقك، بل يكفيك إنشاء فئة مصنع جديد تنتج هذه العناصر وتعدل شيفرة بدء البرنامج قليلًا كي تختار هذه الفئة عند الحاجة إليها.
// تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة.// يطلق على هذه المنتجات اسم "عائلة" وترتبط مع بعضها بعامل مشترك موجود فيها جميعًا.// منتجات العائلة الواحدة قادرة على التعاون فيما بينها.// عائلة المنتجات قد تحتوي بضعة أنواع، لكن منتجات النوع الواحد لا تتوافق مع منتجات نوع آخر.interfaceGUIFactoryismethodcreateButton():ButtonmethodcreateCheckbox():Checkbox// تنتج المصانع الحقيقية عائلة منتجات تنتمي إلى نوع واحد،ويضمن المصنع أن المنتجات متوافقة.// توقيعات أساليب المصنع الحقيقي تعيد منتجًا مجردً بينما يُمثَّل منتج حقيقي داخل الأسلوب.classWinFactoryimplementsGUIFactoryismethodcreateButton():ButtonisreturnnewWinButton()methodcreateCheckbox():CheckboxisreturnnewWinCheckbox()// كل مصنع حقيقي له نوع متوافق معه من المنتجات.classMacFactoryimplementsGUIFactoryismethodcreateButton():ButtonisreturnnewMacButton()methodcreateCheckbox():CheckboxisreturnnewMacCheckbox()// (base interface) يجب أن يكون لكل منتج مستقل من عائلة المنتجات واجهةً أساسية.// ويجب أن تستخدم جميع صور المنتج هذه الواجهة.interfaceButtonismethodpaint()// تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المتوافقة معها.classWinButtonimplementsButtonismethodpaint()is// أخرج زرًا لنظام ويندوز.classMacButtonimplementsButtonismethodpaint()is// أخرج زرًا لنظام ماك.// إليك الواجهة الأساسية لمنتج آخر، يمكن لجميع المنتجات أن تتفاعل مع بعضها، لكن التفاعلات// الصحيحةغير ممكنة إلا بين منتجات نفس النوع الحقيقي.interfaceCheckboxismethodpaint()classWinCheckboximplementsCheckboxismethodpaint()is// أخرج صندوق اختيار بأسلوب ويندوز.classMacCheckboximplementsCheckboxismethodpaint()is// أخرج صندوق اختيار بأسلوب نظام ماك.// Checkbox و Button و GUIFactory تعمل شيفرة العميل مع المصانع والمنتجات من خلال أنواع مجردة فقط هي.// يسمح هذا لك بتمرير أي فئة فرعية لمصنع أو منتج إلى شيفرة العميل دون تعطيلها.classApplicationisprivatefieldbutton:ButtonconstructorApplication(factory:GUIFactory)isthis.factory=factorymethodcreateUI()isthis.button=factory.createButton()methodpaint()isbutton.paint()// يختار التطبيق نوع المصنع بناءً على الإعدادات الحالية أو إعدادات البيئة، وينشئه في// -وقت التشغيل -غالبًا في مرحلة البدء.classApplicationConfiguratorismethodmain()isconfig=readApplicationConfigFile()if(config.OS=="Windows")thenfactory=newWinFactory()elseif(config.OS=="Web")thenfactory=newMacFactory()elsethrownewException("Error! Unknown operating system.")Applicationapp=newApplication(factory)
قابلية التطبيق
استخدم نمط المصنع المجرد عندما تحتاج شيفرتك إلى العمل مع عائلات مختلفة من المنتجات المرتبطة لكنك لا تريد لها أن تعتمد على الفئات الحقيقية لتلك المنتجات، إذ قد تكون مجهولة ابتداءً أو أنك تريد التمهيد للتوسع المستقبلي.
يزودك المصنع المجرد بواجهة لإنشاء الكائنات من كل فئة في عائلة المنتجات، وطالما أن شيفرتك تنشئ كائنات من خلال تلك الواجهة فلا تقلق بشأن إنشاء نوع خاطئ لمنتج لا يطابق المنتجات التي أنشأها تطبيقك.
استخدم المصنع المجرد عندما يكون لديك فئة فيها مجموعة من أساليب المصنع التي تشوش مسؤوليتها الأساسية.
تكون كل فئة في أي برنامج حسن التصميم مسؤولة عن شيء واحد فقط، وحين تتعامل إحدى الفئات مع عدة أنواع من المنتجات فمن المناسب استخراج أساليب المصنع الخاصة بها إلى فئة مصنع مستقلة أو أن تستخدم أسلوب المصنع المجرد هنا بشكل كامل.
كيفية الاستخدام
ارسم مصفوفة من أصناف المنتجات المختلفة في مقابل الأشكال المختلفة من تلك المنتجات.
صرِّح عن واجهات منتجات مجردة لكل أصناف المنتجات، ثم اجعل كل فئات المنتج الحقيقي تستخدم تلك الواجهات.
صرِّح عن واجهة المصنع المجرد مع مجموعة من أساليب الإنشاء لكل المنتجات المجردة.
استخدم مجموعة من فئات المصنع الحقيقي، واحدة لكل شكل من أشكال المنتج.
أنشئ شيفرة بدء المصنع في مكان ما في التطبيق، يجب أن تمثّل هذه الشيفرة إحدى فئات المصنع الحقيقي بناءً على إعدادات التطبيق أو البيئة الحالية. ينبغي أن تمرِّر كائن المصنع ذاك إلى كل الفئات التي تنشئ منتجات.
ابحث في الشيفرة عن كل الاستدعاءات المباشرة لمنشئات المنتجات (product constructors) واستبدلهم باستدعاءات إلى أسلوب الإنشاء المناسب في كائن المصنع.
المزايا والعيوب
المزايا
تكون متأكدًا أن المنتجات التي تحصل عليها من المصنع تتوافق مع بعضها.
تتجنب الربط الوثيق بين المنتجات الحقيقية وشيفرة العميل.
مبدأ المسؤولية الواحدة. تستطيع استخراج شيفرة إنشاء المنتج إلى مكان واحد جاعلًا دعم الشيفرة أسهل.
مبدأ المفتوح/المغلق. تستطيع إدخال أشكال جديدة من المنتج دون تعطيل شيفرة العميل الموجودة مسبقًا.
العيوب
قد تصبح الشيفرة معقدة أكثر مما يجب بسبب إدخال واجهات وفئات كثيرة إلى النمط.
العلاقات مع الأنماط الأخرى
تستخدم تصميمات كثيرة أسلوب المصنع (Factory Method) بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص عبر الفئات الفرعية، ثم تنتقل إلى أسلوب المصنع المجرد أو النموذج الأولي (prototype) أو الباني (Builder)، حيث أنهم أكثر مرونة لكن أكثر تعقيدًا في المقابل.
يركز نمط الباني (Builder) على إنشاء كائنات معقدة خطوة بخطوة، أما المصنع المجرد فيتخصص في إنشاء عائلات من المنتجات المتعلقة ببعضها، كذلك يعيد المنتج فورًا في حين أن الباني يسمح لك بإجراء بعض خطوات الإنشاء الإضافية قبل الحصول على المنتج.
تُبنى فئات المصنع المجرد في الغالب على مجموعة من أساليب المصنع لكن تستطيع استخدام نمط النموذج الأولي لتشكيل الأساليب في تلك الفئات.
قد يُستخدم المصنع المجرد كبديل لنمط الواجهة حين تريد إخفاء طريقة إنشاء كائنات النظام الفرعي عن شيفرة العميل.
من المفيد استخدام المصنع المجرد مع نمط الجسر في الحالة التي لا تعمل فيها بعض المجرَّدات (abstractions) التي يعرِّفها نمط الجسر إلا مع استخدامات محددة، ففي تلك الحالة يمكن للمصنع المجرد أن يغلّف هذه العلاقات ويخفي التعقيد من شيفرة العميل.
يمكن استخدام المصانع المجردة والبانيات (Builders) والنماذج الأولية كمفردات.
الاستخدام في لغة جافا
المستوى: متوسط
الانتشار: واسع الانتشار
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة جافا إذ يُستخدم في مكتبات وأُطر عمل عديدة لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية. إليك بعض الأمثلة من مكتبات جافا:
يمكن ملاحظة هذا النمط من خلال أساليبه التي تعيد كائنات مصنع، ومن ثم يُستخدم المصنع لإنشاء مكونات فرعية.
مثال: إنتاج عائلات من عناصر واجهة متعددة المنصات
ستتصرف الأزرار وصناديق الاختيار في هذا المثال كمنتجات لها شكلين: ويندوز، ونظام ماك. يعرّف المصنع المجرد واجهة لإنشاء الأزرار وصناديق الاختيار، يوجد مصنعيْن حقيقيَّيْن يعيدان المنتجين في شكل واحد. وتعمل شيفرة العميل مع المصانع والمنتجات باستخدام واجهات مجردة، هذه الواجهات تبقي شيفرة العميل عاملة مع عدة أشكال للمنتج اعتمادًا على نوع كائن المصنع.
الأزرار: هرمية أول منتج
buttons/Button.java
packagerefactoring_guru.abstract_factory.example.buttons;/** * (Button/Checkbox) يفترض المصنع المجرد أن لديك عدة عائلات من المنتجات مقسمة إلى هرميات فئوية مستقلة * وكل المنتجات من نفس العائلة لها نفس الواجهة. * * هذه هي الواجهة المشتركة لعائلة الأزرار. */publicinterfaceButton{voidpaint();}
buttons/MacOSButton.java
packagerefactoring_guru.abstract_factory.example.buttons;/** * كل عائلات المنتجات لها نفس الأشكال: ويندوز/ماك. * * وهذا شكل الزر في نظام ماك. */publicclassMacOSButtonimplementsButton{@Overridepublicvoidpaint(){System.out.println("You have created MacOSButton.");}}
buttons/WindowsButton.java
packagerefactoring_guru.abstract_factory.example.buttons;/** * كل عائلات المنتجات لها نفس الأشكال: ويندوز/ماك. * * هذا شكل آخر للزر. */publicclassWindowsButtonimplementsButton{@Overridepublicvoidpaint(){System.out.println("You have created WindowsButton.");}}
صناديق الاختيار: هرمية ثاني منتج
checkboxes/Checkbox.java
packagerefactoring_guru.abstract_factory.example.checkboxes;/** * صناديق الاختيار هي ثاني عائلة منتجات، ولها نفس الأشكال كالأزرار. */publicinterfaceCheckbox{voidpaint();}
checkboxes/MacOSCheckbox.java
packagerefactoring_guru.abstract_factory.example.checkboxes;/** * كل عائلات المنتجات لها نفس الأشكال: ويندوز/ماك. * * هذا أحد أشكال صندوق الاختيار. */publicclassMacOSCheckboximplementsCheckbox{@Overridepublicvoidpaint(){System.out.println("You have created MacOSCheckbox.");}}
checkboxes/WindowsCheckbox.java
packagerefactoring_guru.abstract_factory.example.checkboxes;/** * كل عائلات المنتجات لها نفس الأشكال: ويندوز/ماك. * * هذا شكل آخر لصندوق الاختيار. */publicclassWindowsCheckboximplementsCheckbox{@Overridepublicvoidpaint(){System.out.println("You have created WindowsCheckbox.");}}
المصانع
factories/GUIFactory.java: المصنع المجرد
packagerefactoring_guru.abstract_factory.example.factories;importrefactoring_guru.abstract_factory.example.buttons.Button;importrefactoring_guru.abstract_factory.example.checkboxes.Checkbox;/** * يعرف المصنع المجرد جميع الأنواع المجردة للمنتج. */publicinterfaceGUIFactory{ButtoncreateButton();CheckboxcreateCheckbox();}
factories/MacOSFactory.java: المصنع الحقيقي (نظام ماك)
packagerefactoring_guru.abstract_factory.example.factories;importrefactoring_guru.abstract_factory.example.buttons.Button;importrefactoring_guru.abstract_factory.example.buttons.MacOSButton;importrefactoring_guru.abstract_factory.example.checkboxes.Checkbox;importrefactoring_guru.abstract_factory.example.checkboxes.MacOSCheckbox;/** * يوسّع كل مصنع حقيقي مصنعًا أساسيًا ويكون مسؤولًا عن إنشاء * منتجات من صنف واحد. */publicclassMacOSFactoryimplementsGUIFactory{@OverridepublicButtoncreateButton(){returnnewMacOSButton();}@OverridepublicCheckboxcreateCheckbox(){returnnewMacOSCheckbox();}}
factories/WindowsFactory.java: المصنع الحقيقي (ويندوز)
packagerefactoring_guru.abstract_factory.example.factories;importrefactoring_guru.abstract_factory.example.buttons.Button;importrefactoring_guru.abstract_factory.example.buttons.WindowsButton;importrefactoring_guru.abstract_factory.example.checkboxes.Checkbox;importrefactoring_guru.abstract_factory.example.checkboxes.WindowsCheckbox;/** * كل مصنع حقيقي يوسع مصنعًا أساسيًا ويكون مسؤولًا عن إنشاء منتجات * من صنف واحد. */publicclassWindowsFactoryimplementsGUIFactory{@OverridepublicButtoncreateButton(){returnnewWindowsButton();}@OverridepublicCheckboxcreateCheckbox(){returnnewWindowsCheckbox();}}
التطبيق (App)
app/Application.java: شيفرة العميل
packagerefactoring_guru.abstract_factory.example.app;importrefactoring_guru.abstract_factory.example.buttons.Button;importrefactoring_guru.abstract_factory.example.checkboxes.Checkbox;importrefactoring_guru.abstract_factory.example.factories.GUIFactory;/** * مستخدمو المصنع لا يهمهم أي مصنع حقيقي يستخدمونه طالما أنهم يعملون مع مصانع * ومنتجات من خلال واجهات مجردة. */publicclassApplication{privateButtonbutton;privateCheckboxcheckbox;publicApplication(GUIFactoryfactory){button=factory.createButton();checkbox=factory.createCheckbox();}publicvoidpaint(){button.paint();checkbox.paint();}}
Demo.java: إعدادات التطبيق
packagerefactoring_guru.abstract_factory.example;importrefactoring_guru.abstract_factory.example.app.Application;importrefactoring_guru.abstract_factory.example.factories.GUIFactory;importrefactoring_guru.abstract_factory.example.factories.MacOSFactory;importrefactoring_guru.abstract_factory.example.factories.WindowsFactory;/** * (Demo) يجتمع كل شيء هنا في فئة العرض. */publicclassDemo{/** * يختار التطبيق نوع المصنع وينشئه في وقت التشغيل (عادة في مرحلة البدء) بناءً * على الإعدادات أو البيئة الحالية. */privatestaticApplicationconfigureApplication(){Applicationapp;GUIFactoryfactory;StringosName=System.getProperty("os.name").toLowerCase();if(osName.contains("mac")){factory=newMacOSFactory();app=newApplication(factory);}else{factory=newWindowsFactory();app=newApplication(factory);}returnapp;}publicstaticvoidmain(String[]args){Applicationapp=configureApplication();app.paint();}}
OutputDemo.txt: نتائج التنفيذ
You create WindowsButton.
You created WindowsCheckbox.
الاستخدام في لغة #C
المستوى: متوسط
الانتشار: واسع الانتشار
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة #C إذ تستخدمه عدة مكتبات وأطر عمل لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية.
يمكن ملاحظة النمط من خلال أساليبه التي تعيد كائن المصنع، ثم يستخدم هذا الكائن لإنشاء عناصر فرعية محددة.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط المصنع المجرد، ويركز على إجابة الأسئة التالية:
ما الفئات التي يتكون منها؟
ما الأدوار التي تلعبها هذه الفئات؟
كيف ترتبط عناصر النمط ببعضها؟
Program.cs: مثال هيكلي
usingSystem;namespaceRefactoringGuru.DesignPatterns.AbstractFactory.Structural{interfaceIAbstractFactory{IAbstractProductACreateProductA();IAbstractProductBCreateProductB();}classConcreteFactory1:IAbstractFactory{publicIAbstractProductACreateProductA(){returnnewConcreteProductA1();}publicIAbstractProductBCreateProductB(){returnnewConcreteProductB1();}}classConcreteFactory2:IAbstractFactory{publicIAbstractProductACreateProductA(){returnnewConcreteProductA2();}publicIAbstractProductBCreateProductB(){returnnewConcreteProductB2();}}interfaceIAbstractProductA{stringUsefulFunctionA();}classConcreteProductA1:IAbstractProductA{publicstringUsefulFunctionA(){return"The result of the product A1.";}}classConcreteProductA2:IAbstractProductA{publicstringUsefulFunctionA(){return"The result of the product A2.";}}interfaceIAbstractProductB{stringUsefulFunctionB();stringAnotherUsefulFunctionB(IAbstractProductAcollaborator);}classConcreteProductB1:IAbstractProductB{publicstringUsefulFunctionB(){return"The result of the product B1.";}publicstringAnotherUsefulFunctionB(IAbstractProductAcollaborator){varresult=collaborator.UsefulFunctionA();return$"The result of the B1 collaborating with the ({result})";}}classConcreteProductB2:IAbstractProductB{publicstringUsefulFunctionB(){return"The result of the product B2.";}publicstringAnotherUsefulFunctionB(IAbstractProductAcollaborator){varresult=collaborator.UsefulFunctionA();return$"The result of the B2 collaborating with the ({result})";}}classClient{publicvoidMain(){Console.WriteLine("Client: Testing client code with the first factory type...");ClientMethod(newConcreteFactory1());Console.WriteLine();Console.WriteLine("Client: Testing the same client code with the second factory type...");ClientMethod(newConcreteFactory2());}publicvoidClientMethod(IAbstractFactoryfactory){varproductA=factory.CreateProductA();varproductB=factory.CreateProductB();Console.WriteLine(productB.UsefulFunctionB());Console.WriteLine(productB.AnotherUsefulFunctionB(productA));}}classProgram{staticvoidMain(string[]args){newClient().Main();}}}
Output.txt: المخرجات
Client: Testing client code with the first factory type...
The result of the product B1.
The result of the B1 collaborating with the (The result of the product A1.)
Client: Testing the same client code with the second factory type...
The result of the product B2.
The result of the B2 collaborating with the (The result of the product A2.)
الاستخدام في لغة PHP
المستوى: متوسط
الانتشار: واسع الانتشار
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة PHP إذ تستخدمه عدة مكتبات وأطر عمل لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية.
مثال: بنية النمط
يوضح هذا المثال بنية نمط المصنع المجرد، ويركز على إجابة الأسئة التالية:
ما الفئات التي يتكون منها؟
ما الأدوار التي تلعبها هذه الفئات؟
كيف ترتبط عناصر النمط ببعضها؟
AbstractFactoryStructural.php: مثال هيكلي
<?phpnamespaceRefactoringGuru\AbstractFactory\Structural;/** * تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة. * يطلق على هذه المنتجات اسم عائلة وترتبط مع بعضها بعامل مشترك فيها جميعًا * منتجات العائلة الواحدة قادرة على التعاون فيما بينها * عائلة المنتجات قد تحتوي بضعة أنواع، لكن منتجات النوع الواحد لا تتوافق مع منتجات * نوع آخر */interfaceAbstractFactory{publicfunctioncreateProductA():AbstractProductA;publicfunctioncreateProductB():AbstractProductB;}/** * تنتج المصانع الحقيقية عائلة منتجات تنتمي إلى نوع واحد،ويضمن المصنع أن المنتجات * متوافقة. * توقيعات أساليب المصنع الحقيقي تعيد منتجًا مجردً بينما يُمثَّل منتج حقيقي داخل الأسلوب. */classConcreteFactory1implementsAbstractFactory{publicfunctioncreateProductA():AbstractProductA{returnnewConcreteProductA1;}publicfunctioncreateProductB():AbstractProductB{returnnewConcreteProductB1;}}/** * كل مصنع حقيقي له نوع متوافق معه من المنتجات. */classConcreteFactory2implementsAbstractFactory{publicfunctioncreateProductA():AbstractProductA{returnnewConcreteProductA2;}publicfunctioncreateProductB():AbstractProductB{returnnewConcreteProductB2;}}/** * يجب أن يكون لكل منتج مستقل من عائلة المنتجات واجهةً أساسية * ويجب أن تستخدم جميع صور المنتج هذه الواجهة. */interfaceAbstractProductA{publicfunctionusefulFunctionA():string;}/** * تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المتوافقة معها. */classConcreteProductA1implementsAbstractProductA{publicfunctionusefulFunctionA():string{return"The result of the product A1.";}}classConcreteProductA2implementsAbstractProductA{publicfunctionusefulFunctionA():string{return"The result of the product A2.";}}/** * إليك الواجهة الأساسية لمنتج آخر، يمكن لجميع المنتجات أن تتفاعل مع بعضها، لكن * التفاعلات الصحيحةغير ممكنة إلا بين منتجات نفس النوع الحقيقي. */interfaceAbstractProductB{/** * أن يقوم بوظيفته productB يستطيع */publicfunctionusefulFunctionB():string;/** * ProductA لكن في نفس الوقت يستطيع التعاون مع ... * * يتأكد المصنع المجرد أن كل المنتجات التي ينشئها تكون من نفس الشكل ومن ثم تكون * متوافقة */publicfunctionanotherUsefulFunctionB(AbstractProductA$collaborator):string;}/** * تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المتوافقة معها. */classConcreteProductB1implementsAbstractProductB{publicfunctionusefulFunctionB():string{return"The result of the product B1.";}/** * A1 يعمل بنجاح فقط مع الشكل B1 الشكل * كوسيط AbstractProductA لكنه يقبل أي شكل من أشكال المنتج */publicfunctionanotherUsefulFunctionB(AbstractProductA$collaborator):string{$result=$collaborator->usefulFunctionA();return"The result of the B1 collaborating with the ({$result})";}}classConcreteProductB2implementsAbstractProductB{publicfunctionusefulFunctionB():string{return"The result of the product B2.";}/** * Product A2 يعمل بنجاح فقط مع الشكل Product B2 الشكل * كوسيط AbstractProductA لكنه يقبل أي شكل من أشكال المنتج */publicfunctionanotherUsefulFunctionB(AbstractProductA$collaborator):string{$result=$collaborator->usefulFunctionA();return"The result of the B2 collaborating with the ({$result})";}}/** * لا تعمل شيفرة العميل مع المصانع والمنتجات إلا من خلال الأنواع المجردة التالية: * AbstractFactory و Abstractproduct. * هذا يسمح لك بتمرير أي فئة فرعية لمصنع أو منتج إلى شيفرة العميل دون تعطيلها. */functionclientCode(AbstractFactory$factory){$product_a=$factory->createProductA();$product_b=$factory->createProductB();echo$product_b->usefulFunctionB()."\n";echo$product_b->anotherUsefulFunctionB($product_a)."\n";}/** * تستطيع شيفرة العميل أن تعمل مع أي فئة مصنع حقيقي. */echo"Client: Testing client code with the first factory type:\n";clientCode(newConcreteFactory1);echo"\n";echo"Client: Testing the same client code with the second factory type:\n";clientCode(newConcreteFactory2);
Output.txt: المخرجات
Client: Testing client code with the first factory type:
The result of the product B1.
The result of the B1 collaborating with the (The result of the product A1.)
Client: Testing the same client code with the second factory type:
The result of the product B2.
The result of the B2 collaborating with the (The result of the product A2.)
مثال: حالة حقيقية
في هذا المثالن يوفر نمط المصنع المجرد بنية تحتية لإنشاء الأنواع المختلفة من القوالب للعناصر المختلفة في صفحة الويب، ويستطيع تطبيق الويب أن يدعم محركات الإخراج (rendering engine)، لذلك فإن عناصر التطبيق يجب أن تتواصل مع عناصر القالب فقط من خلال واجهاتها المجردة. كذلك يجب ألا تنشئ شيفرتك عناصر القالب مباشرة، لكن تحيل إنشاءها إلى عناصر مصنع خاصة. أخيرًا يجب ألا تعتمد شيفرتك على عناصر المصنع، بل تعمل معها من خلال واجهة المصنع المجرد.
وكنتيجة لهذا فستكون قادرًا على تزويد التطبيق بعنصر مصنع متوافق مع أحد محركات الإخراج، وكل القوالب ستُنشأ في التطبيق بواسطة ذلك المصنع، وستطابق أنواعها نوع المصنع. وإن قررت تغيير محرك الإخراج فستكون قادرًا على تمرير مصنع جديد إلى شيفرة العميل دون تعطيل الشيفرة الحالية.
AbstractFactoryRealWorld.php: حالة حقيقية
<?phpnamespaceRefactoringGuru\AbstractFactory\RealWorld;/** * نمط تصميم المصنع المجرد * * الهدف: توفير واجهة لإنشاء عائلات من الكائنات المستقلة المرتبطة ببعضها دون تحديد * فئاتها الحقيقية. * * مثال: في هذا المثال، يوفر نموذج المصنع المجرد بنية تحتية لإنشاء أنواع مختلفة من * القوالب لعدة عناصر مختلفة في صفحة ويب. * * يستطيع تطبيق الويب أن يدعم محركات إخراج مختلفة في نفس الوقت، لكن فقط في حالة إن * كانت فئاته مستقلة عن الفئات الحقيقية لمحركات الإخراج، لذا يجب أن تتواصل كائنات * التطبيق مع كائنات القوالب فقط ومن خلال واجهاتها المجردة. كذلك يجب ألا تنتج شيفرُتك * كائنات القوالب مباشرة، بل تفوض إنشاءها إلى كائنات مصنع خاصة. * أخيرًا، يجب ألا تعتمد شيفرتك على كائنات المصنع، وإنما تعملَ معها من خلال واجهة * المصنع المجرد. * * وكنتيجة لهذا فستكون قادرًا على تزويد التطبيق بكائن المصنع الذي يتوافق مع أحد * محركات الإخراج، وكل القوالب التي تُنشأ في التطبيق ستُنشأ بواسطة ذلك المصنع، وسيطابق * نوعها نوع المصنع. * إن قررت تغيير محرك الإخراج ستكون قادرًا على تمرير مصنع جديد إلى شيفرة العميل دون * تعطيل الشيفرة الحالية. *//** * واجهة المصنع المجرد تصرح عن أساليب إنشائية لكل نوع مستقل للمنتج */interfaceTemplateFactory{publicfunctioncreateTitleTemplate():TitleTemplate;publicfunctioncreatePageTemplate():PageTemplate;}/** * كل مصنع حقيقي يتوافق مع شكل أو عائلة محددة من المنتجات. * Twig ينشئ هذا المصنع الحقيقي قوالب */classTwigTemplateFactoryimplementsTemplateFactory{publicfunctioncreateTitleTemplate():TitleTemplate{returnnewTwigTitleTemplate;}publicfunctioncreatePageTemplate():PageTemplate{returnnewTwigPageTemplate($this->createTitleTemplate());}}/** * PHPTemplate وهذا المصنع الحقيقي ينتج قوالب */classPHPTemplateFactoryimplementsTemplateFactory{publicfunctioncreateTitleTemplate():TitleTemplate{returnnewPHPTemplateTitleTemplate;}publicfunctioncreatePageTemplate():PageTemplate{returnnewPHPTemplatePageTemplate($this->createTitleTemplate());}}/** * كل نوع مميز لمنتج يجب أن يكون له واجهة منفصلة، وكل أشكال المنتج يجب أن تتبع * نفس الواجهة. * فمثلًا، هذه الواجهة المجردة للمنتج تصف سلوك قوالب عنوان الصفحة */interfaceTitleTemplate{publicfunctiongetTemplateString():string;}/** * Twig يوفر هذا المنتج الحقيقي قوالب لعنوان صفحة . */classTwigTitleTemplateimplementsTitleTemplate{publicfunctiongetTemplateString():string{return"<h1>{{ title }}</h1>";}}/** * PHPTemplate وهذا المنتج الحقيقي يوفر قوالب لعنوان صفحة. */classPHPTemplateTitleTemplateimplementsTitleTemplate{publicfunctiongetTemplateString():string{return"<h1><?= $title; ?></h1>";}}/** * هذا نوع مجرد آخر من المنتجات يصف قوالب الصفحات الكاملة. */interfacePageTemplate{publicfunctiongetTemplateString():string;}/** * يستخدم قالب الصفحة قالب العنوان -وهو قالب فرعي- لذا علينا أن نوفر طريقة لتهيئته * في كائن القالب الفرعي. سيربط المصنع المجرد قالب الصفحة بقالب عنوان من نفس شكل * المنتج. */abstractclassBasePageTemplateimplementsPageTemplate{protected$titleTemplate;publicfunction__construct(TitleTemplate$titleTemplate){$this->titleTemplate=$titleTemplate;}}/** * من قوالب الصفحات الكاملة Twig شكل . */classTwigPageTemplateextendsBasePageTemplate{publicfunctiongetTemplateString():string{$renderedTitle=$this->titleTemplate->getTemplateString();return<<<HTML <div class="page"> $renderedTitle <article class="content">{{ content }}</article> </div>HTML;}}/** * من قوالب الصفحات الكاملة PHPTemplate شكل . */classPHPTemplatePageTemplateextendsBasePageTemplate{publicfunctiongetTemplateString():string{$renderedTitle=$this->titleTemplate->getTemplateString();return<<<HTML <div class="page"> $renderedTitle <article class="content"><?= $content; ?></article> </div>HTML;}}/** * شيفرة العميل، لاحظ أنها تقبل فئة المصنع المجرد كمعامِل يسمح للعميل بالعمل * مع أي نوع من المصانع الحقيقية. */functiontemplateRenderer(TemplateFactory$factory){$pageTemplate=$factory->createPageTemplate();echo$pageTemplate->getTemplateString();// إليك طرق استخدام القالب في الأمثلة الواقعية:// Twig::render($pageTemplate->getTemplateString(), [// 'title' => $page->title,// 'content' => $page->content, ]);}/** * والآن، في مناطق أخرى من التطبيق، ستقبل شيفرة العميل كائنات المصنع من أي نوع */echo"Testing rendering with the Twig factory:\n";templateRenderer(newTwigTemplateFactory);echo"\n\n";echo"Testing rendering with the PHPTemplate factory:\n";templateRenderer(newPHPTemplateFactory);
Output.txt: المخرجات
Testing rendering with the Twig factory:
<div class="page">
<h1>{{ title }}</h1>
<article class="content">{{ content }}</article>
</div>
Testing rendering with the PHPTemplate factory:
<div class="page">
<h1><?= $title; ?></h1>
<article class="content"><?= $content; ?></article>
</div>