نمط المصنع المجرد
نمط المصنع المجرَّد هو نمط تصميم إنشائي يسمح لك بإنتاج عائلات من الكائنات المرتبطة ببعضها دون تحديد فئاتهم الحقيقية. (انظر ش.1)
المشكلة
تخيل أنك تنشئ محاكيًا لمتجر أثاث، وستتكون شيفرتك من فئات تمثل ما يلي:
- عائلة من المنتجات المرتبطة ببعضها، لنقل
Chair
+Sofa
+CoffeeTable
. - عدة متغيرات من تلك العائلة، فمثلًا ستكون منتجات
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
.
ويجب أن تعمل شيفرة العميل مع كلًّا من المصانع والمنتجات من خلال واجهاتهم المجردة، ذلك يسمح لك بتغيير نوع المصنع الذي تمرره إلى شيفرة العميل وكذلك متغير المنتج الذي تستقبله تلك الشيفرة دون تعطيل شيفرة العميل الفعلية. (انظر ش.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) والنماذج الأولية كمفردات Singletons.
الاستخدام في لغة جافا
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة جافا إذ يُستخدم في مكتبات وأُطر عمل عديدة لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية. إليك بعض الأمثلة من مكتبات جافا:
- 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.Conceptual
{
// تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة.
// يطلق على هذه المنتجات عائلةً وترتبط بسمة أو مبدأ عالي المستوى، وتكون
// منتجات العائلة الواحدة قادرة على العمل فيما بينها، وقد تحتوي عائلة
// المنتجات على عدة أصناف، لكن منتجات صنف ما لا تكون متوافقة مع صنف آخر.
public interface IAbstractFactory
{
IAbstractProductA CreateProductA();
IAbstractProductB CreateProductB();
}
// تنتج المصانع الحقيقية عائلة من المنتجات تنتمي إلى صنف واحد، وتضمن
// العائلة توافق المنتجات الصادرة منها. لاحظ أن توقيعات أساليب المصنع
// (Instantiated) الحقيقي تعيد منتجًا مجردًا، بينما يماثَل المنتج الحقيقي
// داخل الأسلوب.
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();
}
}
// يجب أن يكون لكل منتج مميز من عائلة المنتجات واجهة أساسية.
// ويجب أن تستخدم هذه الواجهة جميع أصناف المنتج.
public 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.";
}
}
// إليك الواجهة الأساسية لمنتج آخر، تستطيع جميع المنتجات أن تتفاعل مع
// بعضها البعض، لكن التفاعل الصحيح لا يكون إلا بين منتجات من نفس الصنف
// الحقيقي.
public interface IAbstractProductB
{
// يستطيع تدبر شؤونه بنفسه B منتج.
string UsefulFunctionB();
// A لكنه يستطيع في نفس الوقت أن يتعاون مع المنتج.
//
// يتأكد المصنع المجرد أن كل المنتجات التي ينشؤها تكون من نفس
// الصنف ومن ثم متوافقة.
string AnotherUsefulFunctionB(IAbstractProductA collaborator);
}
// تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المناسبة لها.
class ConcreteProductB1 : IAbstractProductB
{
public string UsefulFunctionB()
{
return "The result of the product B1.";
}
// ProductA1 لا يعمل بشكل سليم إلا مع الصنف ProductB1 الصنف
// (argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
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.";
}
// ProductA2 لا يعمل بشكل سليم إلا مع الصنف ProductB2 الصنف
// (argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
public string AnotherUsefulFunctionB(IAbstractProductA collaborator)
{
var result = collaborator.UsefulFunctionA();
return $"The result of the B2 collaborating with the ({result})";
}
}
// تعمل شيفرة العميل مع المصانع والمنتجات فقط من خلال أنواع مجردة:
// هذا يسمح لك بتجاوز أي فئة فرعية ، AbstractProduct و AbstractFactory
// لمصنع أو منتج إلى شيفرة العميل دون تعطيلها.
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
{
/**
* أن يقوم بوظيفته B يستطيع منتج
*/
public function usefulFunctionB(): string;
/**
* A لكن في نفس الوقت يستطيع التعاون مع منتج...
*
* يتأكد المصنع المجرد أن كل المنتجات التي ينشئها تكون من نفس الشكل ومن ثم تكون
* متوافقة
*/
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>
الاستخدام في لغة بايثون
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة بايثون إذ تستخدمه عدة مكتبات وأطر عمل لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية.
يمكن ملاحظة النمط من خلال أساليبه التي تعيد كائن المصنع، ثم يستخدم هذا الكائن لإنشاء عناصر فرعية محددة.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط المصنع المجرد، ويركز على إجابة الأسئة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.py: مثال تصوري
from __future__ import annotations
from abc import ABC, abstractmethod
class AbstractFactory(ABC):
"""
تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة.
يطلق على هذه المنتجات عائلةً وترتبط بسمة أو مبدأ عالي المستوى، وتكون
منتجات العائلة الواحدة قادرة على العمل فيما بينها، وقد تحتوي عائلة
المنتجات على عدة أصناف، لكن منتجات صنف ما لا تكون متوافقة مع صنف آخر.
"""
@abstractmethod
def create_product_a(self) -> AbstractProductA:
pass
@abstractmethod
def create_product_b(self) -> AbstractProductB:
pass
class ConcreteFactory1(AbstractFactory):
"""
تنتج المصانع الحقيقية عائلة من المنتجات تنتمي إلى صنف واحد، وتضمن
العائلة توافق المنتجات الصادرة منها. لاحظ أن توقيعات أساليب المصنع
(Instantiated) الحقيقي تعيد منتجًا مجردًا، بينما يماثَل المنتج الحقيقي
داخل الأسلوب.
"""
def create_product_a(self) -> ConcreteProductA1:
return ConcreteProductA1()
def create_product_b(self) -> ConcreteProductB1:
return ConcreteProductB1()
class ConcreteFactory2(AbstractFactory):
"""
كل مصنع حقيقي لديه صنف منتج مناسب له.
"""
def create_product_a(self) -> ConcreteProductA2:
return ConcreteProductA2()
def create_product_b(self) -> ConcreteProductB2:
return ConcreteProductB2()
class AbstractProductA(ABC):
"""
يجب أن يكون لكل منتج مميز من عائلة المنتجات واجهة أساسية.
ويجب أن تستخدم هذه الواجهة جميع أصناف المنتج.
"""
@abstractmethod
def useful_function_a(self) -> str:
pass
"""
تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المناسبة لها.
"""
class ConcreteProductA1(AbstractProductA):
def useful_function_a(self) -> str:
return "The result of the product A1."
class ConcreteProductA2(AbstractProductA):
def useful_function_a(self) -> str:
return "The result of the product A2."
class AbstractProductB(ABC):
"""
إليك الواجهة الأساسية لمنتج آخر، تستطيع جميع المنتجات أن تتفاعل مع
بعضها البعض، لكن التفاعل الصحيح لا يكون إلا بين منتجات من نفس الصنف
الحقيقي.
"""
@abstractmethod
def useful_function_b(self) -> None:
"""
يستطيع تدبر شؤونه بنفسه B منتج...
"""
pass
@abstractmethod
def another_useful_function_b(self, collaborator: AbstractProductA) -> None:
"""
A لكنه يستطيع في نفس الوقت أن يتعاون مع المنتج...
يتأكد المصنع المجرد أن كل المنتجات التي ينشؤها تكون من نفس
الصنف ومن ثم متوافقة.
"""
pass
"""
تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المناسبة لها.
"""
class ConcreteProductB1(AbstractProductB):
def useful_function_b(self) -> str:
return "The result of the product B1."
"""
ProductA1 لا يعمل بشكل سليم إلا مع الصنف ProductB1 الصنف
(argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
"""
def another_useful_function_b(self, collaborator: AbstractProductA) -> str:
result = collaborator.useful_function_a()
return f"The result of the B1 collaborating with the ({result})"
class ConcreteProductB2(AbstractProductB):
def useful_function_b(self) -> str:
return "The result of the product B2."
def another_useful_function_b(self, collaborator: AbstractProductA):
"""
ProductA2 لا يعمل بشكل سليم إلا مع الصنف ProductB2 الصنف
(argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
"""
result = collaborator.useful_function_a()
return f"The result of the B2 collaborating with the ({result})"
def client_code(factory: AbstractFactory) -> None:
"""
تعمل شيفرة العميل مع المصانع والمنتجات فقط من خلال أنواع مجردة:
هذا يسمح لك بتجاوز أي فئة فرعية ، AbstractProduct و AbstractFactory
لمصنع أو منتج إلى شيفرة العميل دون تعطيلها.
"""
product_a = factory.create_product_a()
product_b = factory.create_product_b()
print(f"{product_b.useful_function_b()}")
print(f"{product_b.another_useful_function_b(product_a)}", end="")
if __name__ == "__main__":
"""
تستطيع شيفرة العميل أن تعمل مع أي فئة مصنع حقيقي.
"""
print("Client: Testing client code with the first factory type:")
client_code(ConcreteFactory1())
print("\n")
print("Client: Testing the same client code with the second factory type:")
client_code(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.)
الاستخدام في لغة روبي
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة روبي إذ تستخدمه عدة مكتبات وأطر عمل لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية.
يمكن ملاحظة النمط من خلال أساليبه التي تعيد كائن المصنع، ثم يستخدم هذا الكائن لإنشاء عناصر فرعية محددة.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط المصنع المجرد، ويركز على إجابة الأسئة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.rb: مثال تصوري
# تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة.
# يطلق على هذه المنتجات عائلةً وترتبط بسمة أو مبدأ عالي المستوى، وتكون
# منتجات العائلة الواحدة قادرة على العمل فيما بينها، وقد تحتوي عائلة
# المنتجات على عدة أصناف، لكن منتجات صنف ما لا تكون متوافقة مع صنف آخر.
class AbstractFactory
# @abstract
def create_product_a
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
# @abstract
def create_product_b
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# تنتج المصانع الحقيقية عائلة من المنتجات تنتمي إلى صنف واحد، وتضمن
# العائلة توافق المنتجات الصادرة منها. لاحظ أن توقيعات أساليب المصنع
# (Instantiated) الحقيقي تعيد منتجًا مجردًا، بينما يماثَل المنتج الحقيقي
# داخل الأسلوب.
class ConcreteFactory1 < AbstractFactory
def create_product_a
ConcreteProductA1.new
end
def create_product_b
ConcreteProductB1.new
end
end
# كل مصنع حقيقي لديه صنف منتج مناسب له.
class ConcreteFactory2 < AbstractFactory
def create_product_a
ConcreteProductA2.new
end
def create_product_b
ConcreteProductB2.new
end
end
# يجب أن يكون لكل منتج مميز من عائلة المنتجات واجهة أساسية.
# ويجب أن تستخدم هذه الواجهة جميع أصناف المنتج.
class AbstractProductA
# @abstract
#
# @return [String]
def useful_function_a
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المناسبة لها.
class ConcreteProductA1 < AbstractProductA
def useful_function_a
'The result of the product A1.'
end
end
class ConcreteProductA2 < AbstractProductA
def useful_function_a
'The result of the product A2.'
end
end
# إليك الواجهة الأساسية لمنتج آخر، تستطيع جميع المنتجات أن تتفاعل مع
# بعضها البعض، لكن التفاعل الصحيح لا يكون إلا بين منتجات من نفس الصنف
# الحقيقي.
class AbstractProductB
# يستطيع تدبر شؤونه بنفسه B منتج.
def useful_function_b
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
# A لكنه يستطيع في نفس الوقت أن يتعاون مع المنتج.
#
# يتأكد المصنع المجرد أن كل المنتجات التي ينشؤها تكون من نفس
# الصنف ومن ثم متوافقة.
def another_useful_function_b(_collaborator)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المناسبة لها.
class ConcreteProductB1 < AbstractProductB
# @return [String]
def useful_function_b
'The result of the product B1.'
end
# ProductA1 لا يعمل بشكل سليم إلا مع الصنف ProductB1 الصنف
# (argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
def another_useful_function_b(collaborator)
result = collaborator.useful_function_a
"The result of the B1 collaborating with the (#{result})"
end
end
class ConcreteProductB2 < AbstractProductB
# @return [String]
def useful_function_b
'The result of the product B2.'
end
# ProductA2 لا يعمل بشكل سليم إلا مع الصنف ProductB2 الصنف
# (argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
def another_useful_function_b(collaborator)
result = collaborator.useful_function_a
"The result of the B2 collaborating with the (#{result})"
end
end
# تعمل شيفرة العميل مع المصانع والمنتجات فقط من خلال أنواع مجردة:
# هذا يسمح لك بتجاوز أي فئة فرعية ، AbstractProduct و AbstractFactory
# لمصنع أو منتج إلى شيفرة العميل دون تعطيلها.
def client_code(factory)
product_a = factory.create_product_a
product_b = factory.create_product_b
puts product_b.useful_function_b.to_s
puts product_b.another_useful_function_b(product_a).to_s
end
# تستطيع شيفرة العميل أن تعمل مع أي فئة مصنع حقيقي.
puts 'Client: Testing client code with the first factory type:'
client_code(ConcreteFactory1.new)
puts "\n"
puts 'Client: Testing the same client code with the second factory type:'
client_code(ConcreteFactory2.new)
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.)
الاستخدام في لغة Swift
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة بايثون إذ تستخدمه عدة مكتبات وأطر عمل لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية.
يمكن ملاحظة النمط من خلال أساليبه التي تعيد كائن المصنع، ثم يستخدم هذا الكائن لإنشاء عناصر فرعية محددة.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط المصنع المجرد، ويركز على إجابة الأسئة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift.
Example.swift: مثال تصوري
import XCTest
/// تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة.
/// يطلق على هذه المنتجات عائلةً وترتبط بسمة أو مبدأ عالي المستوى، وتكون
/// منتجات العائلة الواحدة قادرة على العمل فيما بينها، وقد تحتوي عائلة
/// المنتجات على عدة أصناف، لكن منتجات صنف ما لا تكون متوافقة مع صنف آخر.
protocol AbstractFactory {
func createProductA() -> AbstractProductA
func createProductB() -> AbstractProductB
}
/// تنتج المصانع الحقيقية عائلة من المنتجات تنتمي إلى صنف واحد، وتضمن
/// العائلة توافق المنتجات الصادرة منها. لاحظ أن توقيعات أساليب المصنع
/// (Instantiated) الحقيقي تعيد منتجًا مجردًا، بينما يماثَل المنتج الحقيقي
/// داخل الأسلوب.
class ConcreteFactory1: AbstractFactory {
func createProductA() -> AbstractProductA {
return ConcreteProductA1()
}
func createProductB() -> AbstractProductB {
return ConcreteProductB1()
}
}
/// كل مصنع حقيقي لديه صنف منتج مناسب له.
class ConcreteFactory2: AbstractFactory {
func createProductA() -> AbstractProductA {
return ConcreteProductA2()
}
func createProductB() -> AbstractProductB {
return ConcreteProductB2()
}
}
/// يجب أن يكون لكل منتج مميز من عائلة المنتجات واجهة أساسية.
/// ويجب أن تستخدم هذه الواجهة جميع أصناف المنتج.
protocol AbstractProductA {
func usefulFunctionA() -> String
}
/// تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المناسبة لها.
class ConcreteProductA1: AbstractProductA {
func usefulFunctionA() -> String {
return "The result of the product A1."
}
}
class ConcreteProductA2: AbstractProductA {
func usefulFunctionA() -> String {
return "The result of the product A2."
}
}
/// إليك الواجهة الأساسية لمنتج آخر، تستطيع جميع المنتجات أن تتفاعل مع
/// بعضها البعض، لكن التفاعل الصحيح لا يكون إلا بين منتجات من نفس الصنف
/// الحقيقي.
protocol AbstractProductB {
/// يستطيع تدبر شؤونه بنفسه B منتج....
func usefulFunctionB() -> String
/// ... A لكنه يستطيع في نفس الوقت أن يتعاون مع المنتج.
///
/// يتأكد المصنع المجرد أن كل المنتجات التي ينشؤها تكون من نفس
/// الصنف ومن ثم متوافقة.
func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String
}
/// تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المناسبة لها.
class ConcreteProductB1: AbstractProductB {
func usefulFunctionB() -> String {
return "The result of the product B1."
}
/// ProductA1 لا يعمل بشكل سليم إلا مع الصنف ProductB1 الصنف
/// (argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String {
let result = collaborator.usefulFunctionA()
return "The result of the B1 collaborating with the (\(result))"
}
}
class ConcreteProductB2: AbstractProductB {
func usefulFunctionB() -> String {
return "The result of the product B2."
}
/// ProductA2 لا يعمل بشكل سليم إلا مع الصنف ProductB2 الصنف
/// (argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String {
let result = collaborator.usefulFunctionA()
return "The result of the B2 collaborating with the (\(result))"
}
}
/// تعمل شيفرة العميل مع المصانع والمنتجات فقط من خلال أنواع مجردة:
/// هذا يسمح لك بتجاوز أي فئة فرعية ، AbstractProduct و AbstractFactory
/// لمصنع أو منتج إلى شيفرة العميل دون تعطيلها.
class Client {
// ...
static func someClientCode(factory: AbstractFactory) {
let productA = factory.createProductA()
let productB = factory.createProductB()
print(productB.usefulFunctionB())
print(productB.anotherUsefulFunctionB(collaborator: productA))
}
// ...
}
/// لنرى الآن كيف سيعمل كل ذلك.
class AbstractFactoryConceptual: XCTestCase {
func testAbstractFactoryConceptual() {
/// The client code can work with any concrete factory class.
print("Client: Testing client code with the first factory type:")
Client.someClientCode(factory: ConcreteFactory1())
print("Client: Testing the same client code with the second factory type:")
Client.someClientCode(factory: 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.)
مثال واقعي
Example.swift: شيفرة العميل
import Foundation
import UIKit
import XCTest
enum AuthType {
case login
case signUp
}
protocol AuthViewFactory {
static func authView(for type: AuthType) -> AuthView
static func authController(for type: AuthType) -> AuthViewController
}
class StudentAuthViewFactory: AuthViewFactory {
static func authView(for type: AuthType) -> AuthView {
print("Student View has been created")
switch type {
case .login: return StudentLoginView()
case .signUp: return StudentSignUpView()
}
}
static func authController(for type: AuthType) -> AuthViewController {
let controller = StudentAuthViewController(contentView: authView(for: type))
print("Student View Controller has been created")
return controller
}
}
class TeacherAuthViewFactory: AuthViewFactory {
static func authView(for type: AuthType) -> AuthView {
print("Teacher View has been created")
switch type {
case .login: return TeacherLoginView()
case .signUp: return TeacherSignUpView()
}
}
static func authController(for type: AuthType) -> AuthViewController {
let controller = TeacherAuthViewController(contentView: authView(for: type))
print("Teacher View Controller has been created")
return controller
}
}
protocol AuthView {
typealias AuthAction = (AuthType) -> ()
var contentView: UIView { get }
var authHandler: AuthAction? { get set }
var description: String { get }
}
class StudentSignUpView: UIView, AuthView {
private class StudentSignUpContentView: UIView {
/// يحتوي هذا العرض على عدد من المزايا المتاحة أثناء
/// تفويض الطالب.
}
var contentView: UIView = StudentSignUpContentView()
/// سيتم توصيل المعالج من أجل إجراءات أزرار هذا العرض.
var authHandler: AuthView.AuthAction?
override var description: String {
return "Student-SignUp-View"
}
}
class StudentLoginView: UIView, AuthView {
private let emailField = UITextField()
private let passwordField = UITextField()
private let signUpButton = UIButton()
var contentView: UIView {
return self
}
/// سيتم توصيل المعالج من أجل إجراءات أزرار هذا العرض.
var authHandler: AuthView.AuthAction?
override var description: String {
return "Student-Login-View"
}
}
class TeacherSignUpView: UIView, AuthView {
class TeacherSignUpContentView: UIView {
/// يحتوي هذا العرض على عدد من المزايا المتاحة أثناء
/// تفويض المعلم.
}
var contentView: UIView = TeacherSignUpContentView()
/// سيتم توصيل المعالج من أجل إجراءات أزرار هذا العرض.
var authHandler: AuthView.AuthAction?
override var description: String {
return "Teacher-SignUp-View"
}
}
class TeacherLoginView: UIView, AuthView {
private let emailField = UITextField()
private let passwordField = UITextField()
private let loginButton = UIButton()
private let forgotPasswordButton = UIButton()
var contentView: UIView {
return self
}
/// سيتم توصيل المعالج من أجل إجراءات أزرار هذا العرض.
var authHandler: AuthView.AuthAction?
override var description: String {
return "Teacher-Login-View"
}
}
class AuthViewController: UIViewController {
fileprivate var contentView: AuthView
init(contentView: AuthView) {
self.contentView = contentView
super.init(nibName: nil, bundle: nil)
}
required convenience init?(coder aDecoder: NSCoder) {
return nil
}
}
class StudentAuthViewController: AuthViewController {
/// مزايا موجهة للطالب.
}
class TeacherAuthViewController: AuthViewController {
/// مزايا موجهة للطالب.
}
private class ClientCode {
private var currentController: AuthViewController?
private lazy var navigationController: UINavigationController = {
guard let vc = currentController else { return UINavigationController() }
return UINavigationController(rootViewController: vc)
}()
private let factoryType: AuthViewFactory.Type
init(factoryType: AuthViewFactory.Type) {
self.factoryType = factoryType
}
/// العرض
func presentLogin() {
let controller = factoryType.authController(for: .login)
navigationController.pushViewController(controller, animated: true)
}
func presentSignUp() {
let controller = factoryType.authController(for: .signUp)
navigationController.pushViewController(controller, animated: true)
}
/// أساليب أخرى...
}
class AbstractFactoryRealWorld: XCTestCase {
func testFactoryMethodRealWorld() {
#if teacherMode
let clientCode = ClientCode(factoryType: StudentAuthViewFactory.self)
#else
let clientCode = ClientCode(factoryType: TeacherAuthViewFactory.self)
#endif
/// اعرض مخطط تسجيل الدخول
clientCode.presentLogin()
print("Login screen has been presented")
/// اعرض مخطط تسجيل حساب جديد
clientCode.presentSignUp()
print("Sign up screen has been presented")
}
}
Output.txt: نتائج التنفيذ
Teacher View has been created
Teacher View Controller has been created
Login screen has been presented
Teacher View has been created
Teacher View Controller has been created
Sign up screen has been presented
الاستخدام في لغة TypeScript
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يشيع استخدام نمط المصنع المجرد في لغة بايثون إذ تستخدمه عدة مكتبات وأطر عمل لتوفير طريقة لتوسيع وتخصيص عناصرها الأساسية.
يمكن ملاحظة النمط من خلال أساليبه التي تعيد كائن المصنع، ثم يستخدم هذا الكائن لإنشاء عناصر فرعية محددة.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط المصنع المجرد، ويركز على إجابة الأسئة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.ts: مثال تصوري
/**
* تصرح واجهة المصنع المجرد عن مجموعة أساليب تعيد منتجات مجردة مختلفة.
* يطلق على هذه المنتجات عائلةً وترتبط بسمة أو مبدأ عالي المستوى، وتكون
* منتجات العائلة الواحدة قادرة على العمل فيما بينها، وقد تحتوي عائلة
* المنتجات على عدة أصناف، لكن منتجات صنف ما لا تكون متوافقة مع صنف آخر.
*/
interface AbstractFactory {
createProductA(): AbstractProductA;
createProductB(): AbstractProductB;
}
/**
* تنتج المصانع الحقيقية عائلة من المنتجات تنتمي إلى صنف واحد، وتضمن
* العائلة توافق المنتجات الصادرة منها. لاحظ أن توقيعات أساليب المصنع
* (Instantiated) الحقيقي تعيد منتجًا مجردًا، بينما يماثَل المنتج الحقيقي
* داخل الأسلوب.
*/
class ConcreteFactory1 implements AbstractFactory {
public createProductA(): AbstractProductA {
return new ConcreteProductA1();
}
public createProductB(): AbstractProductB {
return new ConcreteProductB1();
}
}
/**
* كل مصنع حقيقي لديه صنف منتج مناسب له.
*/
class ConcreteFactory2 implements AbstractFactory {
public createProductA(): AbstractProductA {
return new ConcreteProductA2();
}
public createProductB(): AbstractProductB {
return new ConcreteProductB2();
}
}
/**
* يجب أن يكون لكل منتج مميز من عائلة المنتجات واجهة أساسية.
* ويجب أن تستخدم هذه الواجهة جميع أصناف المنتج.
*/
interface AbstractProductA {
usefulFunctionA(): string;
}
/**
* تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المناسبة لها.
*/
class ConcreteProductA1 implements AbstractProductA {
public usefulFunctionA(): string {
return 'The result of the product A1.';
}
}
class ConcreteProductA2 implements AbstractProductA {
public usefulFunctionA(): string {
return 'The result of the product A2.';
}
}
/**
* إليك الواجهة الأساسية لمنتج آخر، تستطيع جميع المنتجات أن تتفاعل مع
* بعضها البعض، لكن التفاعل الصحيح لا يكون إلا بين منتجات من نفس الصنف
* الحقيقي.
*/
interface AbstractProductB {
/**
* يستطيع تدبر شؤونه بنفسه B منتج....
*/
usefulFunctionB(): string;
/**
* ...A لكنه يستطيع في نفس الوقت أن يتعاون مع المنتج.
*
* يتأكد المصنع المجرد أن كل المنتجات التي ينشؤها تكون من نفس
* الصنف ومن ثم متوافقة.
*/
anotherUsefulFunctionB(collaborator: AbstractProductA): string;
}
/**
* تُنشأ المنتجات الحقيقية بواسطة المصانع الحقيقية المناسبة لها.
*/
class ConcreteProductB1 implements AbstractProductB {
public usefulFunctionB(): string {
return 'The result of the product B1.';
}
/**
* ProductA1 لا يعمل بشكل سليم إلا مع الصنف ProductB1 الصنف
* (argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
*/
public anotherUsefulFunctionB(collaborator: AbstractProductA): string {
const result = collaborator.usefulFunctionA();
return `The result of the B1 collaborating with the (${result})`;
}
}
class ConcreteProductB2 implements AbstractProductB {
public usefulFunctionB(): string {
return 'The result of the product B2.';
}
/**
* ProductA2 لا يعمل بشكل سليم إلا مع الصنف ProductB2 الصنف
* (argument) كوسيط AbstractProductA لكنه يقبل أي نسخة من
*/
public anotherUsefulFunctionB(collaborator: AbstractProductA): string {
const result = collaborator.usefulFunctionA();
return `The result of the B2 collaborating with the (${result})`;
}
}
/**
* تعمل شيفرة العميل مع المصانع والمنتجات فقط من خلال أنواع مجردة:
* هذا يسمح لك بتجاوز أي فئة فرعية ، AbstractProduct و AbstractFactory
* لمصنع أو منتج إلى شيفرة العميل دون تعطيلها.
*/
function clientCode(factory: AbstractFactory) {
const productA = factory.createProductA();
const productB = factory.createProductB();
console.log(productB.usefulFunctionB());
console.log(productB.anotherUsefulFunctionB(productA));
}
/**
* تستطيع شيفرة العميل أن تعمل مع أي فئة مصنع حقيقي.
*/
console.log('Client: Testing client code with the first factory type...');
clientCode(new ConcreteFactory1());
console.log('');
console.log('Client: Testing the same client code with the second factory type...');
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.)
انظر أيضًا
- نمط المحوِّل Adapter
- نمط المُزخرِف Decorator
- نمط أسلوب المصنع Factory Method
- نمط النموذج الأولي Prototype