نمط المصنع المجرد
نمط المصنع المجرَّد هو نمط تصميم إنشائي يسمح لك بإنتاج عائلات من الكائنات المرتبطة ببعضها دون تحديد فئاتهم الحقيقية. (انظر ش.1)
المشكلة
تخيل أنك تنشئ محاكيًا لمتجر أثاث، وستتكون شيفرتك من فئات تمثل ما يلي:
- عائلة من المنتجات المرتبطة ببعضها، لنقل
Chair
+Sofa
+CoffeeTable
.
عدة متغيرات من تلك العائلة، فمثلًا ستكون منتجات Chair
+ Sofa
+ CoffeeTable
متاحة في المتغيرات التالية: IKEA
، VictorianStyle
، ArtDeco
. (انظر ش.2).
وستحتاج إلى طريقة لإنشاء كائنات أثاث منفردة لتتماشى مع عناصر أخرى من نفس عائلتها، ذلك أن العملاء يغضبون إن استلموا أثاثًا غير متماثل (انظر ش.3). أيضًا ينبغي ألا تغير الشيفرة الموجودة حين تضيف منتجات جديدة أو عائلات منتجات إلى البرنامج، فشركات الأثاث تجدد فهارسها كثيرًا ولا تريد أنت أن تغير شيفرتك في كل مرة تتغير الفهارس!
الحل
أول ما يقترحه نمط المصنع المجرد هو أن تصرح بوضوح عن واجهات لكل نوع من المنتجات في عائلة المنتجات (مثل الكرسي Chair، أو الأريكة Sofa، أو منضدة القهوة Coffee table)، ثم يمكنك بعدها أن تجعل كل متغيرات المنتجات تتبع تلك الواجهات. فمثلًا تستخدم كل متغيرات الكراسي واجهة Chair
، وكل متغيرات مناضد القهوة تستخدم واجهة CoffeeTable
، وهكذا. (انظر ش.4)
dpaf.solution1.png
والخطوة التالية هي التصريح عن واجهة 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)
ويسير الأمر كما يلي، حين يبدأ البرنامج فإنه يتفقد نوع نظام التشغيل الحالي لإنشاء كائن مصنع من فئة تطابق هذا النظام، ثم تستخدم بقية الشيفرة هذا المصنع لإنشاء عناصر الواجهة، ذلك يحول دون إنشاء عناصر خاطئة لا تناسب النظام الحالي. وهكذا لا تعتمد شيفرة العميل على الفئات الحقيقية للمصانع وعناصر الواجهة طالما أنها تعمل مع هذه الكائنات من خلال واجهاتها المجردة، وذلك يسمح أيضًا لشيفرة العميل أن تدعم المصانع الأخرى أو عناصر الواجهة التي قد تضيفها في المستقبل.
وكنتيجة لهذا فلا تحتاج إلى تعديل شيفرة العميل في كل مرة تضيف نوعًا جديدًا من عناصر الواجهة إلى تطبيقك، بل يكفيك إنشاء فئة مصنع جديد تنتج هذه العناصر وتعدل شيفرة بدء البرنامج قليلًا كي تختار هذه الفئة عند الحاجة إليها.
// تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة.
// يطلق على هذه المنتجات اسم "عائلة" وترتبط مع بعضها بعامل مشترك موجود فيها جميعًا.
// منتجات العائلة الواحدة قادرة على التعاون فيما بينها.
// عائلة المنتجات قد تحتوي بضعة أنواع، لكن منتجات النوع الواحد لا تتوافق مع منتجات نوع آخر.
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()
// تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المتوافقة معها.
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)
قابلية التطبيق
- استخدم نمط المصنع المجرد عندما تحتاج شيفرتك إلى العمل مع عائلات مختلفة من المنتجات المرتبطة لكنك لا تريد لها أن تعتمد على الفئات الحقيقية لتلك المنتجات، إذ قد تكون مجهولة ابتداءً أو أنك تريد التمهيد للتوسع المستقبلي.
يزودك المصنع المجرد بواجهة لإنشاء الكائنات من كل فئة في عائلة المنتجات، وطالما أن شيفرتك تنشئ كائنات من خلال تلك الواجهة فلا تقلق بشأن إنشاء نوع خاطئ لمنتج لا يطابق المنتجات التي أنشأها تطبيقك.
- استخدم المصنع المجرد عندما يكون لديك فئة فيها مجموعة من أساليب المصنع التي تشوش مسؤوليتها الأساسية.
تكون كل فئة في أي برنامج حسن التصميم مسؤولة عن شيء واحد فقط، وحين تتعامل إحدى الفئات مع عدة أنواع من المنتجات فمن المناسب استخراج أساليب المصنع الخاصة بها إلى فئة مصنع مستقلة أو أن تستخدم أسلوب المصنع المجرد هنا بشكل كامل.
كيفية الاستخدام
- ارسم مصفوفة من أصناف المنتجات المختلفة في مقابل الأشكال المختلفة من تلك المنتجات.
- صرِّح عن واجهات منتجات مجردة لكل أصناف المنتجات، ثم اجعل كل فئات المنتج الحقيقي تستخدم تلك الواجهات.
- صرِّح عن واجهة المصنع المجرد مع مجموعة من أساليب الإنشاء لكل المنتجات المجردة.
- استخدم مجموعة من فئات المصنع الحقيقي، واحدة لكل شكل من أشكال المنتج.
- أنشئ شيفرة بدء المصنع في مكان ما في التطبيق، يجب أن تمثّل هذه الشيفرة إحدى فئات المصنع الحقيقي بناءً على إعدادات التطبيق أو البيئة الحالية. ينبغي أن تمرِّر كائن المصنع ذاك إلى كل الفئات التي تنشئ منتجات.
- ابحث في الشيفرة عن كل الاستدعاءات المباشرة لمنشئات المنتجات (product constructors) واستبدلهم باستدعاءات إلى أسلوب الإنشاء المناسب في كائن المصنع.
المزايا والعيوب
المزايا
- تكون متأكدًا أن المنتجات التي تحصل عليها من المصنع تتوافق مع بعضها.
- تتجنب الربط الوثيق بين المنتجات الحقيقية وشيفرة العميل.
- مبدأ المسؤولية الواحدة. تستطيع استخراج شيفرة إنشاء المنتج إلى مكان واحد جاعلًا دعم الشيفرة أسهل.
- مبدأ المفتوح/المغلق. تستطيع إدخال أشكال جديدة من المنتج دون تعطيل شيفرة العميل الموجودة مسبقًا.
العيوب
- قد تصبح الشيفرة معقدة أكثر مما يجب بسبب إدخال واجهات وفئات كثيرة إلى النمط.
العلاقات مع الأنماط الأخرى
- تستخدم تصميمات كثيرة أسلوب المصنع (Factory Method) بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص عبر الفئات الفرعية، ثم تنتقل إلى أسلوب المصنع المجرد أو النموذج الأولي (prototype) أو الباني (Builder)، حيث أنهم أكثر مرونة لكن أكثر تعقيدًا في المقابل.
- يركز نمط الباني (Builder) على إنشاء كائنات معقدة خطوة بخطوة، أما المصنع المجرد فيتخصص في إنشاء عائلات من المنتجات المتعلقة ببعضها، كذلك يعيد المنتج فورًا في حين أن الباني يسمح لك بإجراء بعض خطوات الإنشاء الإضافية قبل الحصول على المنتج.
- تُبنى فئات المصنع المجرد في الغالب على مجموعة من أساليب المصنع لكن تستطيع استخدام نمط النموذج الأولي لتشكيل الأساليب في تلك الفئات.
- قد يُستخدم المصنع المجرد كبديل لنمط الواجهة حين تريد إخفاء طريقة إنشاء كائنات النظام الفرعي عن شيفرة العميل.
- من المفيد استخدام المصنع المجرد مع نمط الجسر في الحالة التي لا تعمل فيها بعض المجرَّدات (abstractions) التي يعرِّفها نمط الجسر إلا مع استخدامات محددة، ففي تلك الحالة يمكن للمصنع المجرد أن يغلّف هذه العلاقات ويخفي التعقيد من شيفرة العميل.
- يمكن استخدام المصانع المجردة والبانيات (Builders) والنماذج الأولية كمفردات.
الاستخدام في لغة جافا
المستوى: متوسط
الانتشار: واسع الانتشار
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة جافا إذ يُستخدم في مكتبات وأُطر عمل عديدة لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية. إليك بعض الأمثلة من مكتبات جافا:
- javax.xml.parsers.DocumentBuilderFactory#newInstance()
- javax.xml.transform.TransformerFactory#newInstance()
- javax.xml.xpath.XPathFactory#newInstance()
يمكن ملاحظة هذا النمط من خلال أساليبه التي تعيد كائنات مصنع، ومن ثم يُستخدم المصنع لإنشاء مكونات فرعية.
مثال: إنتاج عائلات من عناصر واجهة متعددة المنصات
ستتصرف الأزرار وصناديق الاختيار في هذا المثال كمنتجات لها شكلين: ويندوز، ونظام ماك. يعرّف المصنع المجرد واجهة لإنشاء الأزرار وصناديق الاختيار، يوجد مصنعيْن حقيقيَّيْن يعيدان المنتجين في شكل واحد. وتعمل شيفرة العميل مع المصانع والمنتجات باستخدام واجهات مجردة، هذه الواجهات تبقي شيفرة العميل عاملة مع عدة أشكال للمنتج اعتمادًا على نوع كائن المصنع.
الأزرار: هرمية أول منتج
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
المستوى: متوسط
الانتشار: واسع الانتشار
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة #C إذ تستخدمه عدة مكتبات وأطر عمل لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية.
يمكن ملاحظة النمط من خلال أساليبه التي تعيد كائن المصنع، ثم يستخدم هذا الكائن لإنشاء عناصر فرعية محددة.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط المصنع المجرد، ويركز على إجابة الأسئة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Program.cs: مثال هيكلي
using System;
namespace RefactoringGuru.DesignPatterns.AbstractFactory.Structural
{
interface IAbstractFactory
{
IAbstractProductA CreateProductA();
IAbstractProductB CreateProductB();
}
class ConcreteFactory1 : IAbstractFactory
{
public IAbstractProductA CreateProductA()
{
return new ConcreteProductA1();
}
public IAbstractProductB CreateProductB()
{
return new ConcreteProductB1();
}
}
class ConcreteFactory2 : IAbstractFactory
{
public IAbstractProductA CreateProductA()
{
return new ConcreteProductA2();
}
public IAbstractProductB CreateProductB()
{
return new ConcreteProductB2();
}
}
interface IAbstractProductA
{
string UsefulFunctionA();
}
class ConcreteProductA1 : IAbstractProductA
{
public string UsefulFunctionA()
{
return "The result of the product A1.";
}
}
class ConcreteProductA2 : IAbstractProductA
{
public string UsefulFunctionA()
{
return "The result of the product A2.";
}
}
interface IAbstractProductB
{
string UsefulFunctionB();
string AnotherUsefulFunctionB(IAbstractProductA collaborator);
}
class ConcreteProductB1 : IAbstractProductB
{
public string UsefulFunctionB()
{
return "The result of the product B1.";
}
public string AnotherUsefulFunctionB(IAbstractProductA collaborator)
{
var result = collaborator.UsefulFunctionA();
return $"The result of the B1 collaborating with the ({result})";
}
}
class ConcreteProductB2 : IAbstractProductB
{
public string UsefulFunctionB()
{
return "The result of the product B2.";
}
public string AnotherUsefulFunctionB(IAbstractProductA collaborator)
{
var result = collaborator.UsefulFunctionA();
return $"The result of the B2 collaborating with the ({result})";
}
}
class Client
{
public void Main()
{
Console.WriteLine("Client: Testing client code with the first factory type...");
ClientMethod(new ConcreteFactory1());
Console.WriteLine();
Console.WriteLine("Client: Testing the same client code with the second factory type...");
ClientMethod(new ConcreteFactory2());
}
public void ClientMethod(IAbstractFactory factory)
{
var productA = factory.CreateProductA();
var productB = factory.CreateProductB();
Console.WriteLine(productB.UsefulFunctionB());
Console.WriteLine(productB.AnotherUsefulFunctionB(productA));
}
}
class Program
{
static void Main(string[] args)
{
new Client().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: مثال هيكلي
<?php
namespace RefactoringGuru\AbstractFactory\Structural;
/**
* تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة.
* يطلق على هذه المنتجات اسم عائلة وترتبط مع بعضها بعامل مشترك فيها جميعًا
* منتجات العائلة الواحدة قادرة على التعاون فيما بينها
* عائلة المنتجات قد تحتوي بضعة أنواع، لكن منتجات النوع الواحد لا تتوافق مع منتجات
* نوع آخر
*/
interface AbstractFactory
{
public function createProductA(): AbstractProductA;
public function createProductB(): AbstractProductB;
}
/**
* تنتج المصانع الحقيقية عائلة منتجات تنتمي إلى نوع واحد،ويضمن المصنع أن المنتجات
* متوافقة.
* توقيعات أساليب المصنع الحقيقي تعيد منتجًا مجردً بينما يُمثَّل منتج حقيقي داخل الأسلوب.
*/
class ConcreteFactory1 implements AbstractFactory
{
public function createProductA(): AbstractProductA
{
return new ConcreteProductA1;
}
public function createProductB(): AbstractProductB
{
return new ConcreteProductB1;
}
}
/**
* كل مصنع حقيقي له نوع متوافق معه من المنتجات.
*/
class ConcreteFactory2 implements AbstractFactory
{
public function createProductA(): AbstractProductA
{
return new ConcreteProductA2;
}
public function createProductB(): AbstractProductB
{
return new ConcreteProductB2;
}
}
/**
* يجب أن يكون لكل منتج مستقل من عائلة المنتجات واجهةً أساسية
* ويجب أن تستخدم جميع صور المنتج هذه الواجهة.
*/
interface AbstractProductA
{
public function usefulFunctionA(): string;
}
/**
* تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المتوافقة معها.
*/
class ConcreteProductA1 implements AbstractProductA
{
public function usefulFunctionA(): string
{
return "The result of the product A1.";
}
}
class ConcreteProductA2 implements AbstractProductA
{
public function usefulFunctionA(): string
{
return "The result of the product A2.";
}
}
/**
* إليك الواجهة الأساسية لمنتج آخر، يمكن لجميع المنتجات أن تتفاعل مع بعضها، لكن
* التفاعلات الصحيحةغير ممكنة إلا بين منتجات نفس النوع الحقيقي.
*/
interface AbstractProductB
{
/**
* أن يقوم بوظيفته productB يستطيع
*/
public function usefulFunctionB(): string;
/**
* ProductA لكن في نفس الوقت يستطيع التعاون مع ...
*
* يتأكد المصنع المجرد أن كل المنتجات التي ينشئها تكون من نفس الشكل ومن ثم تكون
* متوافقة
*/
public function anotherUsefulFunctionB(AbstractProductA $collaborator): string;
}
/**
* تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المتوافقة معها.
*/
class ConcreteProductB1 implements AbstractProductB
{
public function usefulFunctionB(): string
{
return "The result of the product B1.";
}
/**
* A1 يعمل بنجاح فقط مع الشكل B1 الشكل
* كوسيط AbstractProductA لكنه يقبل أي شكل من أشكال المنتج
*/
public function anotherUsefulFunctionB(AbstractProductA $collaborator): string
{
$result = $collaborator->usefulFunctionA();
return "The result of the B1 collaborating with the ({$result})";
}
}
class ConcreteProductB2 implements AbstractProductB
{
public function usefulFunctionB(): string
{
return "The result of the product B2.";
}
/**
* Product A2 يعمل بنجاح فقط مع الشكل Product B2 الشكل
* كوسيط AbstractProductA لكنه يقبل أي شكل من أشكال المنتج
*/
public function anotherUsefulFunctionB(AbstractProductA $collaborator): string
{
$result = $collaborator->usefulFunctionA();
return "The result of the B2 collaborating with the ({$result})";
}
}
/**
* لا تعمل شيفرة العميل مع المصانع والمنتجات إلا من خلال الأنواع المجردة التالية:
* AbstractFactory و Abstractproduct.
* هذا يسمح لك بتمرير أي فئة فرعية لمصنع أو منتج إلى شيفرة العميل دون تعطيلها.
*/
function clientCode(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(new ConcreteFactory1);
echo "\n";
echo "Client: Testing the same client code with the second factory type:\n";
clientCode(new ConcreteFactory2);
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: حالة حقيقية
<?php
namespace RefactoringGuru\AbstractFactory\RealWorld;
/**
* نمط تصميم المصنع المجرد
*
* الهدف: توفير واجهة لإنشاء عائلات من الكائنات المستقلة المرتبطة ببعضها دون تحديد
* فئاتها الحقيقية.
*
* مثال: في هذا المثال، يوفر نموذج المصنع المجرد بنية تحتية لإنشاء أنواع مختلفة من
* القوالب لعدة عناصر مختلفة في صفحة ويب.
*
* يستطيع تطبيق الويب أن يدعم محركات إخراج مختلفة في نفس الوقت، لكن فقط في حالة إن
* كانت فئاته مستقلة عن الفئات الحقيقية لمحركات الإخراج، لذا يجب أن تتواصل كائنات
* التطبيق مع كائنات القوالب فقط ومن خلال واجهاتها المجردة. كذلك يجب ألا تنتج شيفرُتك
* كائنات القوالب مباشرة، بل تفوض إنشاءها إلى كائنات مصنع خاصة.
* أخيرًا، يجب ألا تعتمد شيفرتك على كائنات المصنع، وإنما تعملَ معها من خلال واجهة
* المصنع المجرد.
*
* وكنتيجة لهذا فستكون قادرًا على تزويد التطبيق بكائن المصنع الذي يتوافق مع أحد
* محركات الإخراج، وكل القوالب التي تُنشأ في التطبيق ستُنشأ بواسطة ذلك المصنع، وسيطابق
* نوعها نوع المصنع.
* إن قررت تغيير محرك الإخراج ستكون قادرًا على تمرير مصنع جديد إلى شيفرة العميل دون
* تعطيل الشيفرة الحالية.
*/
/**
* واجهة المصنع المجرد تصرح عن أساليب إنشائية لكل نوع مستقل للمنتج
*/
interface TemplateFactory
{
public function createTitleTemplate(): TitleTemplate;
public function createPageTemplate(): PageTemplate;
}
/**
* كل مصنع حقيقي يتوافق مع شكل أو عائلة محددة من المنتجات.
* Twig ينشئ هذا المصنع الحقيقي قوالب
*/
class TwigTemplateFactory implements TemplateFactory
{
public function createTitleTemplate(): TitleTemplate
{
return new TwigTitleTemplate;
}
public function createPageTemplate(): PageTemplate
{
return new TwigPageTemplate($this->createTitleTemplate());
}
}
/**
* PHPTemplate وهذا المصنع الحقيقي ينتج قوالب
*/
class PHPTemplateFactory implements TemplateFactory
{
public function createTitleTemplate(): TitleTemplate
{
return new PHPTemplateTitleTemplate;
}
public function createPageTemplate(): PageTemplate
{
return new PHPTemplatePageTemplate($this->createTitleTemplate());
}
}
/**
* كل نوع مميز لمنتج يجب أن يكون له واجهة منفصلة، وكل أشكال المنتج يجب أن تتبع
* نفس الواجهة.
* فمثلًا، هذه الواجهة المجردة للمنتج تصف سلوك قوالب عنوان الصفحة
*/
interface TitleTemplate
{
public function getTemplateString(): string;
}
/**
* Twig يوفر هذا المنتج الحقيقي قوالب لعنوان صفحة .
*/
class TwigTitleTemplate implements TitleTemplate
{
public function getTemplateString(): string
{
return "<h1>{{ title }}</h1>";
}
}
/**
* PHPTemplate وهذا المنتج الحقيقي يوفر قوالب لعنوان صفحة.
*/
class PHPTemplateTitleTemplate implements TitleTemplate
{
public function getTemplateString(): string
{
return "<h1><?= $title; ?></h1>";
}
}
/**
* هذا نوع مجرد آخر من المنتجات يصف قوالب الصفحات الكاملة.
*/
interface PageTemplate
{
public function getTemplateString(): string;
}
/**
* يستخدم قالب الصفحة قالب العنوان -وهو قالب فرعي- لذا علينا أن نوفر طريقة لتهيئته
* في كائن القالب الفرعي. سيربط المصنع المجرد قالب الصفحة بقالب عنوان من نفس شكل
* المنتج.
*/
abstract class BasePageTemplate implements PageTemplate
{
protected $titleTemplate;
public function __construct(TitleTemplate $titleTemplate)
{
$this->titleTemplate = $titleTemplate;
}
}
/**
* من قوالب الصفحات الكاملة Twig شكل .
*/
class TwigPageTemplate extends BasePageTemplate
{
public function getTemplateString(): string
{
$renderedTitle = $this->titleTemplate->getTemplateString();
return <<<HTML
<div class="page">
$renderedTitle
<article class="content">{{ content }}</article>
</div>
HTML;
}
}
/**
* من قوالب الصفحات الكاملة PHPTemplate شكل .
*/
class PHPTemplatePageTemplate extends BasePageTemplate
{
public function getTemplateString(): string
{
$renderedTitle = $this->titleTemplate->getTemplateString();
return <<<HTML
<div class="page">
$renderedTitle
<article class="content"><?= $content; ?></article>
</div>
HTML;
}
}
/**
* شيفرة العميل، لاحظ أنها تقبل فئة المصنع المجرد كمعامِل يسمح للعميل بالعمل
* مع أي نوع من المصانع الحقيقية.
*/
function templateRenderer(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(new TwigTemplateFactory);
echo "\n\n";
echo "Testing rendering with the PHPTemplate factory:\n";
templateRenderer(new PHPTemplateFactory);
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>