الفرق بين المراجعتين ل"Design Patterns/factory method"
جميل-بيلوني (نقاش | مساهمات) ط |
|||
(4 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة) | |||
سطر 1: | سطر 1: | ||
− | <noinclude>{{DISPLAYTITLE:نمط أسلوب المصنع}}</noinclude> | + | <noinclude>{{DISPLAYTITLE:نمط أسلوب المصنع Factory Method}}</noinclude> |
− | + | أسلوب المصنع (factory method) هو نمط تصميم إنشائي (creational) يوفر واجهة لإنشاء الكائنات (objects) داخل فئات رئيسة (superclasses) لكنها تسمح في نفس الوقت للفئات الثانوية (subclasses) بتغيير نوع تلك الكائنات التي سيتم إنشاؤها. | |
== المشكلة == | == المشكلة == | ||
− | [[ملف:factory1.png|تصغير|(ش.1) إضافة فئة جديدة إلى البرنامج ليست | + | [[ملف:factory1.png|تصغير|(ش.1) إضافة فئة جديدة إلى البرنامج ليست سهلة إن كانت بقية الشيفرة مقترنة بالفئات الموجودة مسبقًا.]] |
تخيل أنك تنشئ تطبيقًا لشحن البضائع، ولا تتعامل أول نسخة من ذلك التطبيق إلا مع الشحن البري بالشاحنات (Trucks)، فمن المنطقي حينها أن يكون أغلب شيفرتك البرمجية تحت فئة <code>Trucks</code>، لكن لنفرض أن التطبيق اشتهر بعد مدة وتوسعت الطلبات وبدأت تتلقى طلبات شحن بحري، فإنك ستحتاج عندئذ إلى إضافة فئة جديدة ليكن اسمها <code>Ships</code> مثلًا. | تخيل أنك تنشئ تطبيقًا لشحن البضائع، ولا تتعامل أول نسخة من ذلك التطبيق إلا مع الشحن البري بالشاحنات (Trucks)، فمن المنطقي حينها أن يكون أغلب شيفرتك البرمجية تحت فئة <code>Trucks</code>، لكن لنفرض أن التطبيق اشتهر بعد مدة وتوسعت الطلبات وبدأت تتلقى طلبات شحن بحري، فإنك ستحتاج عندئذ إلى إضافة فئة جديدة ليكن اسمها <code>Ships</code> مثلًا. | ||
لكن هذا الأسلوب سيحدِث تغييرات في كل الشيفرة البرمجية لديك، وإن قررت بعد فترة أخرى إضافة نوع آخر من الشحن إلى التطبيق -الجوي مثلًا- فستخوض هذه العملية كلها مرة ثالثة، وستكون النتيجة شيفرة مزدحمة فوضوية تملؤها العبارات الشرطية التي ستغير سلوك التطبيق بناءً على فئة كائنات الشحن. انظر (ش.1) | لكن هذا الأسلوب سيحدِث تغييرات في كل الشيفرة البرمجية لديك، وإن قررت بعد فترة أخرى إضافة نوع آخر من الشحن إلى التطبيق -الجوي مثلًا- فستخوض هذه العملية كلها مرة ثالثة، وستكون النتيجة شيفرة مزدحمة فوضوية تملؤها العبارات الشرطية التي ستغير سلوك التطبيق بناءً على فئة كائنات الشحن. انظر (ش.1) | ||
سطر 13: | سطر 13: | ||
قد يبدو هذا التغيير لا معنى له في البداية، فما زدنا على نقل استدعاء الإنشاء (construction call) من مكان إلى آخر داخل البرنامج، لكن لاحظ الآن أنك تستطيع تخطي أسلوب المصنع داخل فئة فرعية (subclass) وتغير فئة المنتجات التي أنشئت بواسطته. انظر (ش.2) | قد يبدو هذا التغيير لا معنى له في البداية، فما زدنا على نقل استدعاء الإنشاء (construction call) من مكان إلى آخر داخل البرنامج، لكن لاحظ الآن أنك تستطيع تخطي أسلوب المصنع داخل فئة فرعية (subclass) وتغير فئة المنتجات التي أنشئت بواسطته. انظر (ش.2) | ||
− | غير أن الأمر مقيَّد نوعًا ما، فالفئات الفرعية لن تعيد أنواعًا مختلفة من المنتجات إلا إن كانت تلك المنتجات لديها فئة أو واجهة أساسية مشتركة، كذلك | + | غير أن الأمر مقيَّد نوعًا ما، فالفئات الفرعية لن تعيد أنواعًا مختلفة من المنتجات إلا إن كانت تلك المنتجات لديها فئة أو واجهة أساسية مشتركة، كذلك يجب أن يكون نوع الإعادة لأسلوب المصنع في الفئة الأساسية مصرحًا به كالواجهة المشتركة التي ذكرناها. |
[[ملف:solution2-en.png|تصغير|(ش.3) يجب أن تتبع كل المنتجات نفس الواجهة.]] | [[ملف:solution2-en.png|تصغير|(ش.3) يجب أن تتبع كل المنتجات نفس الواجهة.]] | ||
فمثلًا، يجب أن تستخدم الفئتان <code>Truck</code> و <code>Ship</code> واجهة <code>Transport</code> التي تصرح عن أسلوب اسمه <code>deliver</code>، وتنفذ كل فئة هذا الأسلوب بشكل مختلف، فالشاحنات تسلم حمولتها على الأرض، والسفن تسلمها عبر البحار، ويعيد أسلوب المصنع كائنات الشاحنات في فئة <code>RoadLogistics</code>، بينما يعيد سفنًا في فئة <code>SeaLogistics</code>. انظر (ش.3) | فمثلًا، يجب أن تستخدم الفئتان <code>Truck</code> و <code>Ship</code> واجهة <code>Transport</code> التي تصرح عن أسلوب اسمه <code>deliver</code>، وتنفذ كل فئة هذا الأسلوب بشكل مختلف، فالشاحنات تسلم حمولتها على الأرض، والسفن تسلمها عبر البحار، ويعيد أسلوب المصنع كائنات الشاحنات في فئة <code>RoadLogistics</code>، بينما يعيد سفنًا في فئة <code>SeaLogistics</code>. انظر (ش.3) | ||
سطر 19: | سطر 19: | ||
[[ملف:solution3.png|تصغير|(ش.4) طالما أن كل فئات المنتجات تستخدم واجهة مشتركة فيمكنك تمرير كائناتهم إلى شيفرة العميل دون تعطيلها.]] | [[ملف:solution3.png|تصغير|(ش.4) طالما أن كل فئات المنتجات تستخدم واجهة مشتركة فيمكنك تمرير كائناتهم إلى شيفرة العميل دون تعطيلها.]] | ||
− | ولا ترى الشيفرة البرمجية التي تستخدم أسلوب المصنع -قد يطلق عليها عادة شيفرة العميل (client code)- | + | ولا ترى الشيفرة البرمجية التي تستخدم أسلوب المصنع -قد يطلق عليها عادة شيفرة العميل (client code)- أي فرق بين المنتجات الحقيقية التي أعيدت من خلال الفئات الفرعية، ويعامل العميل جميع المنتجات مثل واجهة <code>Transport</code> افتراضية. ويعرف العميل أن كل كائنات النقل يجب أن يكون لديها أسلوب <code>deliver</code>، لكن لا يهم عنده كيفية عملها بالضبط. انظر (ش.4) |
== البُنية == | == البُنية == | ||
سطر 25: | سطر 25: | ||
# يصرّح '''المنتج''' بالواجهة التي ستكون مشتركة لكل الكائنات التي يمكن إنتاجها بواسطة المنشئ (creator) وفئاته الفرعية. | # يصرّح '''المنتج''' بالواجهة التي ستكون مشتركة لكل الكائنات التي يمكن إنتاجها بواسطة المنشئ (creator) وفئاته الفرعية. | ||
# '''المنتجات الحقيقية (concrete products)''' هي الاستخدامات المختلفة لواجهة المنتج. | # '''المنتجات الحقيقية (concrete products)''' هي الاستخدامات المختلفة لواجهة المنتج. | ||
− | # تصرّح فئة '''المنشئ''' عن أسلوب المصنع الذي يعيد كائنات المنتج الجديد، من المهم أن نوع الإعادة من هذا الأسلوب يطابق واجهة المنتج. تستطيع تصريح أسلوب المصنع | + | # تصرّح فئة '''المنشئ''' عن أسلوب المصنع الذي يعيد كائنات المنتج الجديد، من المهم أن نوع الإعادة من هذا الأسلوب يطابق واجهة المنتج. تستطيع تصريح أسلوب المصنع بصفته أسلوبا افتراضيا لإجبار كل الفئات الفرعية على استخدام نسخها الخاص من الأسلوب، وكطريقة بديلة فإن أسلوب المصنع الأساسي يمكنه إعادة بعض الأنواع الافتراضية للمنتجات. لاحظ أن الوظيفة الأساسية للمنشئ (creator) '''ليست إنشاء المنتجات''' رغم التسمية التي توحي بهذا، فإن فئة المنشئ عادة يكون لها منطق عمل متعلق بالمنتجات، ويساعد أسلوب المصنع في فصل هذا المنطق عن فئات المنتجات الحقيقية. إليك مثالًا يوضح الأمر، تستطيع شركات البرمجيات الكبيرة توفير أقسام لتدريب المبرمجين، لكن ذلك لا يعني أن الوظيفة الأساسية للشركة ككل هي إنتاج مبرمجين، بل كتابة البرامج هي وظيفتها. |
− | # تتخطى '''المنشئات الحقيقية (Concrete Creators)''' أسلوب المصنع الأساسي لذا تعيد نوعًا مختلفًا من المنتجات. لاحظ أن أسلوب المصنع قد لا '''ينشئ''' حالات جديدة دومًا، فقد يعيد أحيانًا كائنات موجودة من مصادر مختلفة | + | # تتخطى '''المنشئات الحقيقية (Concrete Creators)''' أسلوب المصنع الأساسي لذا تعيد نوعًا مختلفًا من المنتجات. لاحظ أن أسلوب المصنع قد لا '''ينشئ''' حالات جديدة دومًا، فقد يعيد أحيانًا كائنات موجودة من مصادر مختلفة مثل الذاكرة المؤقتة (Cache memory) أو حوض الكائنات (object pool)، أو غيرها. |
== مثال توضيحي == | == مثال توضيحي == | ||
سطر 34: | سطر 34: | ||
تستخدم فئة الصندوق الحواري الأساسية عناصر واجهة مختلفة لإخراج نافذتها، وقد تبدو تلك العناصر بشكل مختلف لكل نظام تشغيل، لكنها ستتصرف بشكل ثابت فيهم جميعًا، فالزر الذي يظهر لك في ويندوز هو نفس الزر الذي سيظهر لك في لينكس. | تستخدم فئة الصندوق الحواري الأساسية عناصر واجهة مختلفة لإخراج نافذتها، وقد تبدو تلك العناصر بشكل مختلف لكل نظام تشغيل، لكنها ستتصرف بشكل ثابت فيهم جميعًا، فالزر الذي يظهر لك في ويندوز هو نفس الزر الذي سيظهر لك في لينكس. | ||
− | وحين يأتي دور أسلوب المصنع فلن تضطر إلى إعادة كتابة محتوى الصندوق الحواري لكل نظام تشغيل، فإن صرحنا أسلوبَ مصنع ينتج أزرارًا داخل فئة الصندوق الحواري الأساسية، فيمكننا لاحقًا إنشاء فئة صندوق حواري فرعية تعيد أزارًا بأسلوب ويندوز من أسلوب | + | وحين يأتي دور أسلوب المصنع فلن تضطر إلى إعادة كتابة محتوى الصندوق الحواري لكل نظام تشغيل، فإن صرحنا أسلوبَ مصنع ينتج أزرارًا داخل فئة الصندوق الحواري الأساسية، فيمكننا لاحقًا إنشاء فئة صندوق حواري فرعية تعيد أزارًا بأسلوب ويندوز من أسلوب المصنع وتكتسب الفئة الفرعية عندئد أغلب شيفرة الصندوق الحواري من الفئة الأساسية لكنها ستُخرج أزرارًا بأسلوب ويندوز على الشاشة بسبب أسلوب المصنع. |
− | ولكي يعمل هذا النمط، يجب أن تعمل فئة الصندوق الحواري الأساسية مع أزرار افتراضية: | + | ولكي يعمل هذا النمط، يجب أن تعمل فئة الصندوق الحواري الأساسية مع أزرار افتراضية: مثل فئة أساسية أو واجهة تتبعها كل أزرار المنتجات الحقيقية، وبهذه الطريقة تظل شيفرة الصندوق الحواري عاملة بغض النظر عن نوع الأزرار التي تعمل معها. |
بالطبع يمكنك تطبيق هذا المنظور على عناصر الواجهة الأخرى كذلك، لكن مع كل أسلوب مصنع تضيفه إلى الصندوق الحواري فإنك تقترب أكثر من نمط [[Design Patterns/abstract factory|المصنع المجرد]].<syntaxhighlight lang="java"> | بالطبع يمكنك تطبيق هذا المنظور على عناصر الواجهة الأخرى كذلك، لكن مع كل أسلوب مصنع تضيفه إلى الصندوق الحواري فإنك تقترب أكثر من نمط [[Design Patterns/abstract factory|المصنع المجرد]].<syntaxhighlight lang="java"> | ||
سطر 101: | سطر 101: | ||
// تعمل الشيفرة الحالية للعميل مع نسخة من منشئ حقيقي، ولو من خلال واجهته الأساسية، فطالما أن العميل | // تعمل الشيفرة الحالية للعميل مع نسخة من منشئ حقيقي، ولو من خلال واجهته الأساسية، فطالما أن العميل | ||
− | // يظل عاملًا مع المنشئ من خلال واجهة | + | // يظل عاملًا مع المنشئ من خلال واجهة أساسية، فيمكنك تمرير أي فئة منشئ فرعية إليه. |
method main() is | method main() is | ||
dialog.initialize() | dialog.initialize() | ||
سطر 488: | سطر 488: | ||
'''الانتشار: ★ ★ ★''' | '''الانتشار: ★ ★ ★''' | ||
− | '''أمثلة الاستخدام''': يستخدم نمط أسلوب المصنع بكثرة في شيفرة | + | '''أمثلة الاستخدام''': يستخدم نمط أسلوب المصنع بكثرة في شيفرة [[PHP]]، وهو مفيد في كتابة شيفرة عالية المرونة. |
=== مثال: بُنية النمط === | === مثال: بُنية النمط === |
المراجعة الحالية بتاريخ 10:45، 7 أكتوبر 2022
أسلوب المصنع (factory method) هو نمط تصميم إنشائي (creational) يوفر واجهة لإنشاء الكائنات (objects) داخل فئات رئيسة (superclasses) لكنها تسمح في نفس الوقت للفئات الثانوية (subclasses) بتغيير نوع تلك الكائنات التي سيتم إنشاؤها.
المشكلة
تخيل أنك تنشئ تطبيقًا لشحن البضائع، ولا تتعامل أول نسخة من ذلك التطبيق إلا مع الشحن البري بالشاحنات (Trucks)، فمن المنطقي حينها أن يكون أغلب شيفرتك البرمجية تحت فئة Trucks
، لكن لنفرض أن التطبيق اشتهر بعد مدة وتوسعت الطلبات وبدأت تتلقى طلبات شحن بحري، فإنك ستحتاج عندئذ إلى إضافة فئة جديدة ليكن اسمها Ships
مثلًا.
لكن هذا الأسلوب سيحدِث تغييرات في كل الشيفرة البرمجية لديك، وإن قررت بعد فترة أخرى إضافة نوع آخر من الشحن إلى التطبيق -الجوي مثلًا- فستخوض هذه العملية كلها مرة ثالثة، وستكون النتيجة شيفرة مزدحمة فوضوية تملؤها العبارات الشرطية التي ستغير سلوك التطبيق بناءً على فئة كائنات الشحن. انظر (ش.1)
الحل
يقترح أسلوب المصنع هنا أن تستبدل الاستدعاءات المباشرة لإنشاء الكائنات -من خلال معامِل new
- باستدعاءات لأسلوب factory الخاص، ما سيحدث هو أن الكائنات سيتم إنشاؤها بمعامل new
كما تقدم لكنها ستُستَدعى من داخل أسلوب المصنع. ويشار إلى تلك الكائنات التي تُعاد (returned) بأسلوب المصنع باسم المنتجات (products).
قد يبدو هذا التغيير لا معنى له في البداية، فما زدنا على نقل استدعاء الإنشاء (construction call) من مكان إلى آخر داخل البرنامج، لكن لاحظ الآن أنك تستطيع تخطي أسلوب المصنع داخل فئة فرعية (subclass) وتغير فئة المنتجات التي أنشئت بواسطته. انظر (ش.2)
غير أن الأمر مقيَّد نوعًا ما، فالفئات الفرعية لن تعيد أنواعًا مختلفة من المنتجات إلا إن كانت تلك المنتجات لديها فئة أو واجهة أساسية مشتركة، كذلك يجب أن يكون نوع الإعادة لأسلوب المصنع في الفئة الأساسية مصرحًا به كالواجهة المشتركة التي ذكرناها.
فمثلًا، يجب أن تستخدم الفئتان Truck
و Ship
واجهة Transport
التي تصرح عن أسلوب اسمه deliver
، وتنفذ كل فئة هذا الأسلوب بشكل مختلف، فالشاحنات تسلم حمولتها على الأرض، والسفن تسلمها عبر البحار، ويعيد أسلوب المصنع كائنات الشاحنات في فئة RoadLogistics
، بينما يعيد سفنًا في فئة SeaLogistics
. انظر (ش.3)
ولا ترى الشيفرة البرمجية التي تستخدم أسلوب المصنع -قد يطلق عليها عادة شيفرة العميل (client code)- أي فرق بين المنتجات الحقيقية التي أعيدت من خلال الفئات الفرعية، ويعامل العميل جميع المنتجات مثل واجهة Transport
افتراضية. ويعرف العميل أن كل كائنات النقل يجب أن يكون لديها أسلوب deliver
، لكن لا يهم عنده كيفية عملها بالضبط. انظر (ش.4)
البُنية
- يصرّح المنتج بالواجهة التي ستكون مشتركة لكل الكائنات التي يمكن إنتاجها بواسطة المنشئ (creator) وفئاته الفرعية.
- المنتجات الحقيقية (concrete products) هي الاستخدامات المختلفة لواجهة المنتج.
- تصرّح فئة المنشئ عن أسلوب المصنع الذي يعيد كائنات المنتج الجديد، من المهم أن نوع الإعادة من هذا الأسلوب يطابق واجهة المنتج. تستطيع تصريح أسلوب المصنع بصفته أسلوبا افتراضيا لإجبار كل الفئات الفرعية على استخدام نسخها الخاص من الأسلوب، وكطريقة بديلة فإن أسلوب المصنع الأساسي يمكنه إعادة بعض الأنواع الافتراضية للمنتجات. لاحظ أن الوظيفة الأساسية للمنشئ (creator) ليست إنشاء المنتجات رغم التسمية التي توحي بهذا، فإن فئة المنشئ عادة يكون لها منطق عمل متعلق بالمنتجات، ويساعد أسلوب المصنع في فصل هذا المنطق عن فئات المنتجات الحقيقية. إليك مثالًا يوضح الأمر، تستطيع شركات البرمجيات الكبيرة توفير أقسام لتدريب المبرمجين، لكن ذلك لا يعني أن الوظيفة الأساسية للشركة ككل هي إنتاج مبرمجين، بل كتابة البرامج هي وظيفتها.
- تتخطى المنشئات الحقيقية (Concrete Creators) أسلوب المصنع الأساسي لذا تعيد نوعًا مختلفًا من المنتجات. لاحظ أن أسلوب المصنع قد لا ينشئ حالات جديدة دومًا، فقد يعيد أحيانًا كائنات موجودة من مصادر مختلفة مثل الذاكرة المؤقتة (Cache memory) أو حوض الكائنات (object pool)، أو غيرها.
مثال توضيحي
يوضح المثال كيف يمكن استخدام أسلوب المصنع لإنشاء عناصر واجهة لأكثر من منصة دون الحاجة إلى جمع شيفرة العميل (client code) مع فئات واجهات المنتجات الحقيقية.
تستخدم فئة الصندوق الحواري الأساسية عناصر واجهة مختلفة لإخراج نافذتها، وقد تبدو تلك العناصر بشكل مختلف لكل نظام تشغيل، لكنها ستتصرف بشكل ثابت فيهم جميعًا، فالزر الذي يظهر لك في ويندوز هو نفس الزر الذي سيظهر لك في لينكس.
وحين يأتي دور أسلوب المصنع فلن تضطر إلى إعادة كتابة محتوى الصندوق الحواري لكل نظام تشغيل، فإن صرحنا أسلوبَ مصنع ينتج أزرارًا داخل فئة الصندوق الحواري الأساسية، فيمكننا لاحقًا إنشاء فئة صندوق حواري فرعية تعيد أزارًا بأسلوب ويندوز من أسلوب المصنع وتكتسب الفئة الفرعية عندئد أغلب شيفرة الصندوق الحواري من الفئة الأساسية لكنها ستُخرج أزرارًا بأسلوب ويندوز على الشاشة بسبب أسلوب المصنع.
ولكي يعمل هذا النمط، يجب أن تعمل فئة الصندوق الحواري الأساسية مع أزرار افتراضية: مثل فئة أساسية أو واجهة تتبعها كل أزرار المنتجات الحقيقية، وبهذه الطريقة تظل شيفرة الصندوق الحواري عاملة بغض النظر عن نوع الأزرار التي تعمل معها.
بالطبع يمكنك تطبيق هذا المنظور على عناصر الواجهة الأخرى كذلك، لكن مع كل أسلوب مصنع تضيفه إلى الصندوق الحواري فإنك تقترب أكثر من نمط المصنع المجرد.
// تصرح فئة المنشئ عن أسلوب المصنع الذي يجب أن يعيد أحد الكائنات من فئة المنتج، وتوضح فئات المنشئ الفرعية
// عادة استخدامات لهذا الأسلوب.
class Dialog is
// قد يوفر المنشئ أيضًا بعض الاستخدامات الافتراضية لأسلوب المصنع.
abstract method createButton()
// لاحظ أنه برغم التسمية فإن وظيفة المنشئ الأساسية ليست إنشاء منتجات، لكن له منطق عمل
// يعتمد على كائنات المنتجات المعادة بأسلوب المصنع.
// وتستطيع الفئات الفرعية تغيير منطق العمل ذاك بشكل غير مباشر عن طرق تخطي أسلوب المصنع
// وإعادة نوع مختلف من المنتجات منه.
method renderWindow() is
// استدع أسلوب المصنع لإنشاء كائن منتج.
Button okButton = createButton()
// والآن، استخدم المنتج.
okButton.onClick(closeDialog)
okButton.render()
// المنشئات الحقيقية تتخطى أسلوب المصنع لتغير نوع المنتج.
class WindowsDialog extends Dialog is
method createButton() is
return new WindowsButton()
class WebDialog extends Dialog is
method createButton() is
return new HTMLButton()
// تصرح واجهة المنتج عن العمليات التي يجب أن تستخدمها كل المنتجات الحقيقية.
interface Button is
method render()
method onClick(f)
// توفر المنتجات الحقيقية استخدامات مختلفة لواجهة المنتج.
class WindowsButton implements Button is
method render(a, b) is
// أخرج الزر بأسلوب ويندوز.
method onClick(f) is
// اربط حدث نقرة محلية في نظام التشغيل.
class HTMLButton implements Button is
method render(a, b) is
// Return an HTML representation of a button.
method onClick(f) is
// اربط حدث نقرة في المتصفح.
class Application is
field dialog: Dialog
// يختار التطبيق نوع المنشئ بناءً على الإعدادات الحالية أو إعدادات البيئة.
method initialize() is
config = readApplicationConfigFile()
if (config.OS == "Windows") then
dialog = new WindowsDialog()
else if (config.OS == "Web") then
dialog = new WebDialog()
else
throw new Exception("Error! Unknown operating system.")
// تعمل الشيفرة الحالية للعميل مع نسخة من منشئ حقيقي، ولو من خلال واجهته الأساسية، فطالما أن العميل
// يظل عاملًا مع المنشئ من خلال واجهة أساسية، فيمكنك تمرير أي فئة منشئ فرعية إليه.
method main() is
dialog.initialize()
dialog.render()
قابلية التطبيق
- استخدم أسلوب المصنع عند عدم معرفة أنواع الكائنات التي ستتعامل شيفرتك معها، وكذلك عند عدم معرفة اعتمادياتها.
يفصل أسلوب المصنع شيفرة إنشاء المنتج عن الشيفرة التي تستخدم المنتج، ومن ثم يكون من السهل زيادة شيفرة إنشاء المنتج بشكل منفصل عن بقية الشيفرة. فمثلًان لإضافة نوع جديد من المنتجات إلى التطبيق، لن تحتاج إلا إلى إنشاء فئة creator فرعية جديدة لتتخطى بها أسلوب المصنع داخلها.
- استخدم أسلوب المصنع حين تريد تزويد مستخدمي مكتبتك أو إطار عملك بطريقة لزيادة عناصره الداخلية.
أسهل طريقة لتوسيع السلوك الافتراضي لمكتبة أو إطار عمل هو الاكتساب أو الوراثة (inheritance)، لكن أنّى لإطار العمل معرفة أن فئتك الفرعية يجب أن تُستخدم بدلًا من العنصر القياسي؟
الحل هو تقليل الشيفرة التي ستنشئ العناصر في إطار العمل إلى أسلوب مصنع وحيد، وتسمح لأي كان أن يتخطى تلك الطريقة إضافة إلى توسعة العنصر ذاته.
لنرى كيفية عمل ذلك، تخيل أنك تكتب تطبيقًا باستخدام إطار عمل مفتوح المصدر لواجهة المستخدم، يجب أن تكون الأزرار في تطبيقك دائرية الحواف، لكن إطار العمل لا يوفر سوى أزرار مربعة. الحل هنا أن توسع فئة Button
بفئة فرعية لتكن RoundButton
، لكنك الآن ستضطر إلى إخبار فئة UIFramework
الرئيسية أن تستخدم فئة الأزرار الفرعية الجديدة بدلًا من الافتراضية.
ولفعل ذلك تنشئ فئة فرعية لتكن UIWithRoundButtons
من فئة إطار العمل الأساسية وتتخطى أسلوب createButton
الخاص بها، ويمكنك أن تجعل فئتك الفرعية تعيد كائنات RoundButton
رغم أن هذا الأسلوب يعيد كائنات Button
في فئته الأساسية. والآن لم يتبق سوى أن تستخدم فئة UIWithRoundButtons
بدلًا من UIFramework
.
- استخدم أسلوب المصنع عند توفير موارد النظام بإعادة استخدام الكائنات الموجودة بدلًا من إعادة إنشائهم في كل مرة.
قد تحدث تلك الحالة حين تتعامل مع كائنات كبيرة وتستهلك الموارد مثل اتصالات قواعد البيانات أو أنظمة الملفات أو موارد الشبكات. إليك ما يجب فعله لإعادة استخدام كائن موجود فعلًا:
- أولًا عليك توفير بعض المساحة لمتابعة كل الكائنات المنشأة.
- عندما يطلب شخص أحد الكائنات فإن البرنامج يبحث عن كائن متاح في حوض الكائنات (object pool)، ثم يعيده إلى شيفرة العميل.
- إن لم تكن هناك كائنات متاحة فيجب أن ينشئ البرنامج واحدًا ويضيفه إلى الحوض..
هذه شيفرة طويلة، ويجب أن تجمعها كلها في مكان واحد لكي لا تلوث البرنامج بشيفرة مكررة.
لعل أنسب مكان يمكن أن توضع فيه تلك الشيفرة هو منشئ (constructor) الفئة الذي نحاول أن نعيد استخدام كائناته، لكن يجب أن يعيد المنشئ كائنات جديدة افتراضيًا، فلا يعيد حالات موجودة فعلًا، لذا تحتاج أن يكون لديك أسلوب عادي قادر على إنشاء كائنات جديدة إضافة إلى إعادة استخدام الكائنات الموجودة، وهذا يشبه كثيرًا أسلوب المصنع.
كيفية الاستخدام
- تأكد أن تتبع كل المنتجات نفس الواجهة، ويجب أن تصرح هذا الواجهة عن الأساليب التي تناسب كل منتج.
- أضف أسلوب مصنع فارغ داخل فئة المنشئ (creator)، يجب أن يطابق نوع الإعادة واجهة المنتج المشتركة.
- ابحث عن كل المراجع إلى منشئات المنتج (product constructors) داخل شيفرة المنشئ (creator)، واستبدل كل واحدة فيهم باستدعاءات إلى أسلوب المصنع في نفس الوقت الذي تستخرج فيه شيفرة إنشاء المنتج (creation code) إلى أسلوب المصنع، قد تحتاج إلى إضافة معامل مؤقت لأسلوب المصنع من أجل التحكم في نوع المنتج المُعاد. وقد تبدو شيفرة أسلوب المصنع في هذه المرحلة قبيحة، وقد يكون فيها معامل
switch
كبير يلتقط أي فئة منتج ليمثّلها، لكننا سنحل هذا قريبًا. - أنشئ سلسلة من فئات creator الفرعية لكل نوع من المنتجات الموجودة في أسلوب المصنع، وتخطى أسلوب المصنع في الفئات الفرعية واستخرج الأجزاء المناسبة من شيفرة البناء (construction code) من الأسلوب الأساسي.
- إن كانت أنواع المنتجات كثيرة وليس من المنطقي إنشاء فئات فرعية لهم جميعًا فيمكنك إعادة استخدام معامل التحكم (control parameter) من الفة الأساسية داخل الفئات الفرعية. فمثلًا، تخيل أن لديك الهرمية التالية من الفئات: فئة
Mail
الأساسية مع بضعة فئات فرعية:AirMail
وGroundMail
، فتكون فئات واجهةTransport
هيPlane
وTruck
وTrain
. وبينما تستخدمAirMail
كائنات منPlane
، فإنGroundMail
ستعمل مع كائناتTruck
وTrain
كليهما. يمكنك إنشاء فئات فرعية جديدة (TrainMail
مثلًا) لمعالجة كلا الحالتين، لكن هناك خيار آخر، يمكن لشيفرة العميل أن تمرر وسيطًا (argument) لأسلوب المصنع الخاص بفئةGroundMail
لاختيار أي المنتجات التي يريد استقبالها. - إن أصبح أسلوب المصنع الأساسي فارغًا بعد كل الاستخراجات فيمكنك تحويله ليكون مجردًا (abstract)، أما إن تبقى شيء ما فيمكنك جعله السلوك الافتراضي للأسلوب.
المزايا والعيوب
المزايا
- تتجنب الربط الوثيق بين منتجات المنشئ (creator products) والمنتجات الحقيقية (Concrete Products).
- مبدأ المسؤولية الواحدة. يمكنك نقل شيفرة إنشاء المنتج إلى مكان واحد في البرنامج، لتسهيل عملية دعم الشيفرة.
- مبدأ المفتوح/المغلق. يمكنك إدخال أنواع جديدة من المنتجات داخل البرنامج دون تعطيل شيفرة العميل الحالية.
العيوب
قد تصبح الشيفرة أكثر تعقيدًا بما أنك تحتاج إلى إضافة فئات فرعية كثيرة لتُستخدم داخل النمط، وأفضل حالة ستكون لديك هي عندما تضيف النمط إلى هيكل قائم بالفعل من فئات المنشئ (creator classes).
العلاقات مع الأنماط الأخرى
- تستخدم تصميمات كثيرة أسلوب المصنع بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص باستخدام الفئات الفرعية، ثم تنتقل إلى أسلوب المصنع المجرد أو النموذج الأولي أو الباني (Builder)، حيث أنهم أكثر مرونة لكن أكثر تعقيدًا في المقابل.
- تُبنى فئات المصنع المجرد عادة على سلسلة من أساليب المصنع، لكنك تستطيع استخدام النموذج الأولي لتشكيل الأساليب فوق تلك الفئات.
- يمكنك استخدام أسلوب المصنع إلى جانب المكرِّر لتسمح لمجموعات من الفئات الفرعية بإعادة أنواع مختلفة من المكرٍّرات تتوافق مع المجموعات.
- لا يُبنى النموذج الأولي على الاكتساب (inheritance) لذلك ليس لديه عيوبه، لكن من الناحية الأخرى فإنه يتطلب عملية بدء معقدة من الكائن المستنسخ، أما أسلوب المصنع فمبني على الاكتساب لكنه لا يتطلب خطوة بدء.
- أسلوب المصنع هو استثناء من أسلوب القالب (Template Method)، لكنه قد يخدم كمرحلة داخل أسلوب كبير من نوع القالب.
الاستخدام في لغة جافا
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يستخدم نمط أسلوب المصنع بشكل واسع في شيفرة جافا، فهو مفيد حين تريد أن تكون شيفرتك على درجة عالية من المرونة. ويوجد النمط في مكتبات جافا التالية:
- ()java.util.Calendar#getInstance
- ()java.util.ResourceBundle#getBundle
- ()java.text.NumberFormat#getInstance
- ()java.nio.charset.Charset#forName
- (java.net.URLStreamHandlerFactory#createURLStreamHandler(String تُعيد كائنات مفردة (Singleton) مختلفة بناءً على البروتوكول.
- ()java.util.EnumSet#of
- ()javax.xml.bind.JAXBContext#createMarshaller وأساليب أخرى مشابهة.
يمكن ملاحظة أساليب المصنع من الأساليب الإنشائية التي تنشئ كائنات من فئات حقيقية (concrete classes) لكنها تعيدها ككائنات من نوع مجرد (abstract type) أو واجهة مجردة (abstract interface).
مثال: إنتاج عناصر واجهة رسومية للمنصات المختلفة
تلعب الأزرار دور المنتجات والصناديق الحوارية تمثل دور المنشئات (creators)، تتطلب الأنواع المختلفة من الصناديق الحوارية أنواعها الخاصة من العناصر، هذا هو السبب الذي ننشئ فئة فرعية لكل نوع من أنواع الصناديق الحوارية ونتخطى أساليب المصنع الخاصة بها.
والآن، سيمثّل كل نوع من أنواع الصناديق الحوارية فئة زر مناسبة، ويعمل الصندوق الأساسي مع المنتجات مستخدمًا واجهتهم المشتركة، لهذا تبقى شيفرتها عاملة بعد كل تلك التغييرات.
الأزرار
buttons/Button.java: واجهة مشتركة للمنتج
package refactoring_guru.factory_method.example.buttons;
/**
* واجهة مشتركة لكل الأزرار.
*/
public interface Button {
void render();
void onClick();
}
buttons/HtmlButton.java: منتج حقيقي
package refactoring_guru.factory_method.example.buttons;
/**
* HTML استخدام الزر في.
*/
public class HtmlButton implements Button {
public void render() {
System.out.println("<button>Test Button</button>");
onClick();
}
public void onClick() {
System.out.println("Click! Button says - 'Hello World!'");
}
}
buttons/WindowsButton.java: منتج حقيقي آخر
package refactoring_guru.factory_method.example.buttons;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* استخدام للزر في ويندوز.
*/
public class WindowsButton implements Button {
JPanel panel = new JPanel();
JFrame frame = new JFrame();
JButton button;
public void render() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("Hello World!");
label.setOpaque(true);
label.setBackground(new Color(235, 233, 126));
label.setFont(new Font("Dialog", Font.BOLD, 44));
label.setHorizontalAlignment(SwingConstants.CENTER);
panel.setLayout(new FlowLayout(FlowLayout.CENTER));
frame.getContentPane().add(panel);
panel.add(label);
onClick();
panel.add(button);
frame.setSize(320, 200);
frame.setVisible(true);
onClick();
}
public void onClick() {
button = new JButton("Exit");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frame.setVisible(false);
System.exit(0);
}
});
}
}
المصنع
factory/Dialog.java: منشئ الأساس (Base Creator)
package refactoring_guru.factory_method.example.factory;
import refactoring_guru.factory_method.example.buttons.Button;
/**
* فئة المصنع الأساسية، لاحظ أن المصنع هنا مجرد دور للفئة، ينبغي أن يكون له منطق عمل يحتاج منتجات مختلفة
* لكي يُنشأ.
*/
public abstract class Dialog {
public void renderWindow() {
// ... other code ...
Button okButton = createButton();
okButton.render();
}
/**
* ستتخطى الفئات الفرعية هذا الأسلوب من أجل إنشاء كائنات أزرار محددة
*/
public abstract Button createButton();
}
factory/HtmlDialog.java: منشئ حقيقي
package refactoring_guru.factory_method.example.factory;
import refactoring_guru.factory_method.example.buttons.Button;
import refactoring_guru.factory_method.example.buttons.HtmlButton;
/**
* HTML الحواري أزرار HTML سينتِج صندوق.
*/
public class HtmlDialog extends Dialog {
@Override
public Button createButton() {
return new HtmlButton();
}
}
factory/WindowsDialog.java: منشئ حقيقي آخر
package refactoring_guru.factory_method.example.factory;
import refactoring_guru.factory_method.example.buttons.Button;
import refactoring_guru.factory_method.example.buttons.WindowsButton;
/**
* صندوق ويندوز الحواري سيُنتِج أزرار ويندوز.
*/
public class WindowsDialog extends Dialog {
@Override
public Button createButton() {
return new WindowsButton();
}
}
Demo.java: شيفرة العميل
package refactoring_guru.factory_method.example;
import refactoring_guru.factory_method.example.factory.Dialog;
import refactoring_guru.factory_method.example.factory.HtmlDialog;
import refactoring_guru.factory_method.example.factory.WindowsDialog;
/**
* فئة عينة العرض، كل شيء يُجمَّع هنا.
*/
public class Demo {
private static Dialog dialog;
public static void main(String[] args) {
configure();
runBusinessLogic();
}
/**
* يُختار المصنع الحقيقي عادة بناءً على الإعدادات أو خيارات البيئة.
*/
static void configure() {
if (System.getProperty("os.name").equals("Windows 10")) {
dialog = new WindowsDialog();
} else {
dialog = new HtmlDialog();
}
}
/**
* يجب أن تعمل شيفرة العميل مع المصانع والمنتجات عبر واجهات نظرية،
* فبهذه الطريقة لا يهم أي مصنع تعمل معه ولا نوع المنتج الذي تعيده.
*/
static void runBusinessLogic() {
dialog.renderWindow();
}
}
OutputDemo.txt: نتائج التنفيذ (صندوق HTML)
<button>Test Button</button>
Click! Button says - 'Hello World!'
OutputDemo.png: نتائج التنفيذ (صندوق حواري في ويندوز)
الاستخدام في لغة #C
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يستخدم أسلوب المصنع بكثرة في شيفرة #C، وهو مفيد في كتابة شيفرة عالية المرونة.
كما تقدم في شرح الاستخدام في لغة جافا، يمكن ملاحظة أساليب المصنع من الأساليب الإنشائية التي تنشئ كائنات من فئات حقيقية (concrete classes) لكنها تعيدها ككائنات من نوع مجرد (abstract type) أو واجهة مجردة (abstract interface).
مثال: بُنية النمط
يوضح هذاالمثال بنية نمط أسلوب المصنع، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Program.cs: مثال على البنية
using System;
namespace RefactoringGuru.DesignPatterns.FactoryMethod.Structural
{
abstract class Creator
{
public abstract IProduct FactoryMethod();
public string SomeOperation()
{
var product = FactoryMethod();
var result = "Creator: The same creator's code has just worked with " + product.Operation();
return result;
}
}
class ConcreteCreator1 : Creator
{
public override IProduct FactoryMethod()
{
return new ConcreteProduct1();
}
}
class ConcreteCreator2 : Creator
{
public override IProduct FactoryMethod()
{
return new ConcreteProduct2();
}
}
interface IProduct
{
string Operation();
}
class ConcreteProduct1 : IProduct
{
public string Operation()
{
return "{Result of ConcreteProduct1}";
}
}
class ConcreteProduct2 : IProduct
{
public string Operation()
{
return "{Result of ConcreteProduct2}";
}
}
class Client
{
public void Main()
{
Console.WriteLine("App: Launched with the ConcreteCreator1.");
ClientMethod(new ConcreteCreator1());
Console.WriteLine("");
Console.WriteLine("App: Launched with the ConcreteCreator2.");
ClientMethod(new ConcreteCreator2());
}
public void ClientMethod(Creator creator)
{
// ...
Console.WriteLine("Client: I'm not aware of the creator's class, but it still works.\n"
+ creator.SomeOperation());
// ...
}
}
class Program
{
static void Main(string[] args)
{
new Client().Main();
}
}
}
Output.txt: المخرجات
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of ConcreteProduct2}
الاستخدام في لغة PHP
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يستخدم نمط أسلوب المصنع بكثرة في شيفرة PHP، وهو مفيد في كتابة شيفرة عالية المرونة.
مثال: بُنية النمط
يوضح هذاالمثال بنية نمط أسلوب المصنع، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
بعد تعلم بنية النمط سيكون من السهل استيعاب المثال التالي بناءً على مثال لحالة حقيقية في PHP.
FactoryMethodStructural.php: مثال على البنية
<?php
namespace RefactoringGuru\FactoryMethod\Structural;
/**
* تصرح فئة المنشئ عن أسلوب المصنع الذي يجب أن يعيد كائنًا من فئة أحد المنتجات
* وتوضح الفئة الفرعية للمنشئ في الغالب استخدامات هذا الأسلوب.
*/
abstract class Creator
{
/**
* لاحظ أن المنشئ قد يوضح بعض الاستخدامات الافتراضية لأسلوب المصنع.
*/
public abstract function factoryMethod(): Product;
/**
* لاحظ كذلك أن وظيفة المنشئ الأساسية ليست إنشاء المنتجات، رغم مايوحي به الاسم،
* لكن له عادة منطق عمل يعتمد على كائنات المنتج المعادة بواسطة أسلوب المصنع.
* وتستطيع الفئات الفرعية تغيير منطق العمل ذاك بشكل غير مباشر عبر تخطي أسلوب
* المصنع وإعادة نوع مختلف من المنتجات منه.
*/
public function someOperation(): string
{
// Call the factory method to create a Product object.
$product = $this->factoryMethod();
// Now, use the product.
$result = "Creator: The same creator's code has just worked with ".
$product->operation();
return $result;
}
}
/**
* تتخطى المنشئات الحقيقية أسلوب المصنع من أجل تغيير نوع المنتج.
*/
class ConcreteCreator1 extends Creator
{
/**
* لاحظ أن توقيع الأسلوب لا يزال يستخدم نوع المنتج النظري برغم أن المنتج الحقيقي مُعاد فعلًا من الأسلوب
* بهذه الطريقة يظل المنشئ مستقلًا من فئات المنتج الحقيقي.
*/
public function factoryMethod(): Product
{
return new ConcreteProduct1();
}
}
class ConcreteCreator2 extends Creator
{
public function factoryMethod(): Product
{
return new ConcreteProduct2();
}
}
/**
* تصرح واجهة المنتج عن العمليات التي يجب أن تستخدمها المنتجات الحقيقية.
*/
interface Product
{
public function operation(): string;
}
/**
* توضح المنتجات الحقيقية الاستخدامات المختلفة لواجهة المنتج.
*/
class ConcreteProduct1 implements Product
{
public function operation(): string
{
return "{Result of the ConcreteProduct1}";
}
}
class ConcreteProduct2 implements Product
{
public function operation(): string
{
return "{Result of the ConcreteProduct2}";
}
}
/**
* تعمل شيفرة العميل مع نسخة من منشئ حقيقي ولو من خلال واجهته الأساسية، فطالما أن العميل يظل
* عاملًا مع المنشئ من خلال واجهة أساسية فيمكنك تمرير أي فئة منشئ فرعية إليه
*/
function clientCode(Creator $creator)
{
// ...
print("Client: I'm not aware of the creator's class, but it still works.\n"
.$creator->someOperation());
// ...
}
/**
* يختار التطبيق نوع منشئ بناءً على الإعدادات أو البيئة.
*/
print("App: Launched with the ConcreteCreator1.\n");
clientCode(new ConcreteCreator1());
print("\n\n");
print("App: Launched with the ConcreteCreator2.\n");
clientCode(new ConcreteCreator2());
Output.txt: المخرجات
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
مثال: حالة حقيقية
في هذا المثال، يوضح نمط أسلوب المصنع واجهة لإنشاء موصلات شبكة اجتماعية يمكن استخدامها لتشجيل الدخول إلى الشبكة وإنشاء منشورات ومن ثم إمكانية تنفيذ نشاطات أخرى، وكل ذلك دون دمج شيفرة العميل مع فئات محددة للشبكة الاجتماعية.
FactoryMethodRealWorld.php: مثال لحالة حقيقية
<?php
namespace RefactoringGuru\FactoryMethod\RealWorld;
/**
* نمط التصميم: أسلوب المصنع
*
* الهدف: تحديد واجهة لإنشاء الكائنات، مع السماح للفئات الفرعية باختيار الفئة التي تمثلها.
* يسمح أسلوب المصنع كذلك للفئة أن ترجئ التمثيل إلى فئات فرعية.
*
* مثال: يوفر نمط أسلوب المصنع هنا واجهة لإنشاء موصلات شبكة اجتماعية يمكن استخدامها لتسجيل الدخول إلى
* الشبكة وإنشاء منشورات ومن ثَمَّ تنفيذ نشاطات أخرى، دون ربط شيفرة العميل بأي فئات محددة للشبكة.
*/
/**
* (constructor) يصرِّح المنشئ عن أسلوب المصنع الذي يمكن استخدامه كبديل لاستدعاءات المنشئ
* المباشرة للمنتجات.
*
* - قبل: $p = new FacebookConnector()
* - بعد: $p = $this->getSocialNetwork()
*
* الفرعية SocialNetworkPoster يسمح هذا بتغيير نوع المنتج المُنشأ بفئات
*/
abstract class SocialNetworkPoster
{
/**
* أسلوب المصنع الفعلي، لاحظ أنه يعيدالموصل النظري.
* يسمح هذا للفئات الفرعية أن تعيد أي موصلات حقيقية دون خرق ميثاق الفئة الرئيسية.
*/
public abstract function getSocialNetwork(): SocialNetworkConnector;
/**
* حين يُستخدم أسلوب المصنع داخل منطق عمل لمنشئ، فإن الفئات الفرعية قد تُغيِّر المنطق بشكل غير مباشر
* من خلال إعادة أنواع موصلات مختلفة من أسلوب المصنع.
*/
public function post($content)
{
// Call the factory method to create a Product object...
$network = $this->getSocialNetwork();
// ...then use it as you will.
//
// ...ثم استخدمها كما تشاء.
$network->logIn();
$network->createPost($content);
$network->logout();
}
}
/**
* 'post' هذا المنتج الحقيقي يدعم فيس بوك، تذكر أن هذه الفئة تكتسب أسلوب
* من الفئة الأم. المنشئات الحقيقية هي الفئات التي يستخدمها العميل فعليًا.
*/
class FacebookPoster extends SocialNetworkPoster
{
private $login, $password;
public function __construct($login, $password)
{
$this->login = $login;
$this->password = $password;
}
public function getSocialNetwork(): SocialNetworkConnector
{
return new FacebookConnector($this->login, $this->password);
}
}
/**
* هذا المنتج الحقيقي يدعم شبكة لينكدإن.
*/
class LinkedInPoster extends SocialNetworkPoster
{
private $email, $password;
public function __construct($email, $password)
{
$this->email = $email;
$this->password = $password;
}
public function getSocialNetwork(): SocialNetworkConnector
{
return new LinkedInConnector($this->email, $this->password);
}
}
/**
* تصرح واجهة المنتج عن سلوك الأنواع المختلفة من المنتجات.
*/
interface SocialNetworkConnector
{
public function logIn();
public function logOut();
public function createPost($content);
}
/**
* فيس بوك API هذا المنتج الحقيقي يستخدم.
*/
class FacebookConnector implements SocialNetworkConnector
{
private $login, $password;
public function __construct($login, $password)
{
$this->login = $login;
$this->password = $password;
}
public function logIn()
{
print("Send HTTP API request to log in user $this->login with " .
"password $this->password\n");
}
public function logOut()
{
print("Send HTTP API request to log out user $this->login\n");
}
public function createPost($content)
{
print("Send HTTP API requests to create a post in Facebook timeline.\n");
}
}
/**
* لينكدإن API هذا المنتج الحقيقي يستخدم.
*/
class LinkedInConnector implements SocialNetworkConnector
{
private $email, $password;
public function __construct($email, $password)
{
$this->email = $email;
$this->password = $password;
}
public function logIn()
{
print("Send HTTP API request to log in user $this->email with " .
"password $this->password\n");
}
public function logOut()
{
print("Send HTTP API request to log out user $this->email\n");
}
public function createPost($content)
{
print("Send HTTP API requests to create a post in LinkedIn timeline.\n");
}
}
/**
* SocialNetworkPoster تستطيع شيفرة العميل أن تعمل مع أي فئة فرعية لـ
* بما أنها لا تعتمد على فئات حقيقية.
*/
function clientCode(SocialNetworkPoster $creator)
{
// ...
$creator->post("Hello world!");
$creator->post("I had a large hamburger this morning!");
// ...
}
/**
* أثناء مرحلة البدء، يمكن للتطبيق اختيار الشبكة التي يريد العمل معها، وينشئ كائنًا من الفئة الفرعية
* المناسبة، ويمرره إلى شيفرة العميل.
*/
print("Testing ConcreteCreator1:\n");
clientCode(new FacebookPoster("john_smith", "******"));
print("\n\n");
print("Testing ConcreteCreator2:\n");
clientCode(new LinkedInPoster("john_smith@example.com", "******"));
Output.txt: المخرجات
Testing ConcreteCreator1:
Send HTTP API request to log in user john_smith with password ******
Send HTTP API requests to create a post in Facebook timeline.
Send HTTP API request to log out user john_smith
Send HTTP API request to log in user john_smith with password ******
Send HTTP API requests to create a post in Facebook timeline.
Send HTTP API request to log out user john_smith
Testing ConcreteCreator2:
Send HTTP API request to log in user john_smith@example.com with password ******
Send HTTP API requests to create a post in LinkedIn timeline.
Send HTTP API request to log out user john_smith@example.com
Send HTTP API request to log in user john_smith@example.com with password ******
Send HTTP API requests to create a post in LinkedIn timeline.
Send HTTP API request to log out user john_smith@example.com
الاستخدام في لغة بايثون
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يستخدم أسلوب المصنع بكثرة في شيفرة بايثون، وهو مفيد في كتابة شيفرة عالية المرونة.
يمكن ملاحظة أساليب المصنع من الأساليب الإنشائية التي تنشئ كائنات من فئات حقيقية (concrete classes) لكنها تعيدها ككائنات من نوع مجرد (abstract type) أو واجهة مجردة (abstract interface).
مثال: بُنية النمط
يوضح هذاالمثال بنية نمط أسلوب المصنع، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.py: مثال تصوري
from __future__ import annotations
from abc import ABC, abstractmethod
class Creator(ABC):
"""
تصرح فئة المنشئ عن أسلوب المصنع الذي يجب أن يعيد كائنًا من فئة أحد المنتجات
وتوضح الفئة الفرعية للمنشئ في الغالب استخدامات هذا الأسلوب.
"""
@abstractmethod
def factory_method(self):
"""
لاحظ أن المنشئ قد يوضح بعض الاستخدامات الافتراضية لأسلوب المصنع.
"""
pass
def some_operation(self) -> str:
"""
لاحظ كذلك أن وظيفة المنشئ الأساسية ليست إنشاء المنتجات، رغم مايوحي به الاسم،
لكن له عادة منطق عمل يعتمد على كائنات المنتج المعادة
بواسطة أسلوب المصنع.
وتستطيع الفئات الفرعية تغيير منطق العمل ذاك بشكل غير مباشر
عبر تخطي أسلوب المصنع وإعادة نوع مختلف من المنتجات معه.
"""
# استدع أسلوب المصنع لإنشاء كائن منتج.
product = self.factory_method()
# والآن، استخدم المنتج.
result = f"Creator: The same creator's code has just worked with {product.operation()}"
return result
"""
تتخطى المنشئات الحقيقية أسلوب المصنع من أجل تغيير نوع المنتج.
"""
class ConcreteCreator1(Creator):
"""
لاحظ أن توقيع الأسلوب لا يزال يستخدم نوع المنتج النظري برغم
أن المنتج الحقيقي مُعاد فعلًا من الأسلوب، وبهذه الطريقة يظل
المنشئ مستقلًا من فئات المنتج الحقيقي.
"""
def factory_method(self) -> ConcreteProduct1:
return ConcreteProduct1()
class ConcreteCreator2(Creator):
def factory_method(self) -> ConcreteProduct2:
return ConcreteProduct2()
class Product(ABC):
"""
تصرح واجهة المنتج عن العمليات التي يجب أن تستخدمها المنتجات الحقيقية.
"""
@abstractmethod
def operation(self) -> str:
pass
"""
توضح المنتجات الحقيقية الاستخدامات المختلفة لواجهة المنتج.
"""
class ConcreteProduct1(Product):
def operation(self) -> str:
return "{Result of the ConcreteProduct1}"
class ConcreteProduct2(Product):
def operation(self) -> str:
return "{Result of the ConcreteProduct2}"
def client_code(creator: Creator) -> None:
"""
تعمل شيفرة العميل مع نسخة من منشئ حقيقي ولو من خلال واجهته
الأساسية، فطالما أن العميل يظل عاملًا مع المنشئ من خلال
واجهة أساسية فيمكنك تمرير أي فئة منشئ فرعية إليه.
"""
print(f"Client: I'm not aware of the creator's class, but it still works.\n"
f"{creator.some_operation()}", end="")
if __name__ == "__main__":
print("App: Launched with the ConcreteCreator1.")
client_code(ConcreteCreator1())
print("\n")
print("App: Launched with the ConcreteCreator2.")
client_code(ConcreteCreator2())
Output.txt: نتائج التنفيذ
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
الاستخدام في لغة روبي
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يستخدم أسلوب المصنع بكثرة في شيفرة روبي، وهو مفيد في كتابة شيفرة عالية المرونة.
يمكن ملاحظة أساليب المصنع من الأساليب الإنشائية التي تنشئ كائنات من فئات حقيقية (concrete classes) لكنها تعيدها ككائنات من نوع مجرد (abstract type) أو واجهة مجردة (abstract interface).
مثال: بُنية النمط
يوضح هذاالمثال بنية نمط أسلوب المصنع، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.rb: مثال تصوري
# تصرح فئة المنشئ عن أسلوب المصنع الذي يجب أن يعيد كائنًا من فئة أحد المنتجات
# وتوضح الفئة الفرعية للمنشئ في الغالب استخدامات هذا الأسلوب.
class Creator
# لاحظ أن المنشئ قد يوضح بعض الاستخدامات الافتراضية لأسلوب المصنع.
def factory_method
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
# لاحظ كذلك أن وظيفة المنشئ الأساسية ليست إنشاء المنتجات، رغم مايوحي به الاسم،
# لكن له عادة منطق عمل يعتمد على كائنات المنتج المعادة
# بواسطة أسلوب المصنع.
# وتستطيع الفئات الفرعية تغيير منطق العمل ذاك بشكل غير مباشر
# عبر تخطي أسلوب المصنع وإعادة نوع مختلف من المنتجات معه.
def some_operation
# استدع أسلوب المصنع لإنشاء كائن منتج.
product = factory_method
# والآن، استخدم المنتج.
result = "Creator: The same creator's code has just worked with #{product.operation}"
result
end
end
# تتخطى المنشئات الحقيقية أسلوب المصنع من أجل تغيير نوع المنتج.
class ConcreteCreator1 < Creator
# لاحظ أن توقيع الأسلوب لا يزال يستخدم نوع المنتج النظري برغم
# أن المنتج الحقيقي مُعاد فعلًا من الأسلوب، وبهذه الطريقة يظل
# المنشئ مستقلًا من فئات المنتج الحقيقي.
def factory_method
ConcreteProduct1.new
end
end
class ConcreteCreator2 < Creator
# @return [ConcreteProduct2]
def factory_method
ConcreteProduct2.new
end
end
# تصرح واجهة المنتج عن العمليات التي يجب أن تستخدمها المنتجات الحقيقية.
class Product
# return [String]
def operation
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# توضح المنتجات الحقيقية الاستخدامات المختلفة لواجهة المنتج.
class ConcreteProduct1 < Product
# @return [String]
def operation
'{Result of the ConcreteProduct1}'
end
end
class ConcreteProduct2 < Product
# @return [String]
def operation
'{Result of the ConcreteProduct2}'
end
end
# تعمل شيفرة العميل مع نسخة من منشئ حقيقي ولو من خلال واجهته
# الأساسية، فطالما أن العميل يظل عاملًا مع المنشئ من خلال
# واجهة أساسية فيمكنك تمرير أي فئة منشئ فرعية إليه.
def client_code(creator)
print "Client: I'm not aware of the creator's class, but it still works.\n"\
"#{creator.some_operation}"
end
puts 'App: Launched with the ConcreteCreator1.'
client_code(ConcreteCreator1.new)
puts "\n\n"
puts 'App: Launched with the ConcreteCreator2.'
client_code(ConcreteCreator2.new)
output.txt: نتائج التنفيذ
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
الاستخدام في لغة Swift
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يستخدم أسلوب المصنع بكثرة في شيفرة Swift، وهو مفيد في كتابة شيفرة عالية المرونة.
يمكن ملاحظة أساليب المصنع من الأساليب الإنشائية التي تنشئ كائنات من فئات حقيقية (concrete classes) لكنها تعيدها ككائنات من نوع مجرد (abstract type) أو واجهة مجردة (abstract interface).
مثال: بُنية النمط
يوضح هذاالمثال بنية نمط أسلوب المصنع، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift.
Example.swift: مثال تصوري
import XCTest
/// تصرح فئة المنشئ عن أسلوب المصنع الذي يجب أن يعيد كائنًا من فئة أحد المنتجات
/// وتوضح الفئة الفرعية للمنشئ في الغالب استخدامات هذا الأسلوب.
protocol Creator {
/// لاحظ أن المنشئ قد يوضح بعض الاستخدامات الافتراضية لأسلوب المصنع.
func factoryMethod() -> Product
/// لاحظ كذلك أن وظيفة المنشئ الأساسية ليست إنشاء المنتجات، رغم مايوحي به الاسم،
/// لكن له عادة منطق عمل يعتمد على كائنات المنتج المعادة
/// بواسطة أسلوب المصنع.
/// وتستطيع الفئات الفرعية تغيير منطق العمل ذاك بشكل غير مباشر
عبر تخطي أسلوب المصنع وإعادة نوع مختلف من المنتجات معه.
func someOperation() -> String
}
/// تستخدم هذه الإضافة السلوك الافتراضي للمنشئ، يمكن تجاوز
/// هذا الأسلوب في الفئات الفرعية.
extension Creator {
func someOperation() -> String {
// استدع أسلوب المصنع لإنشاء كائن منتج.
let product = factoryMethod()
// والآن، استخدم المنتج.
return "Creator: The same creator's code has just worked with " + product.operation()
}
}
/// تتخطى المنشئِات الحقيقية أسلوب المصنع من أجل تغيير نوع المنتج.
class ConcreteCreator1: Creator {
/// لاحظ أن توقيع الأسلوب لا يزال يستخدم نوع المنتج النظري برغم
/// أن المنتج الحقيقي مُعاد فعلًا من الأسلوب، وبهذه الطريقة يظل
/// المنشئ مستقلًا من فئات المنتج الحقيقي.
public func factoryMethod() -> Product {
return ConcreteProduct1()
}
}
class ConcreteCreator2: Creator {
public func factoryMethod() -> Product {
return ConcreteProduct2()
}
}
/// تصرح واجهة المنتج عن العمليات التي يجب أن تستخدمها المنتجات الحقيقية.
protocol Product {
func operation() -> String
}
/// توضح المنتجات الحقيقية الاستخدامات المختلفة لواجهة المنتج.
class ConcreteProduct1: Product {
func operation() -> String {
return "{Result of the ConcreteProduct1}"
}
}
class ConcreteProduct2: Product {
func operation() -> String {
return "{Result of the ConcreteProduct2}"
}
}
/// تعمل شيفرة العميل مع نسخة من منشئ حقيقي ولو من خلال واجهته
/// الأساسية، فطالما أن العميل يظل عاملًا مع المنشئ من خلال
/// واجهة أساسية فيمكنك تمرير أي فئة منشئ فرعية إليه.
class Client {
// ...
static func someClientCode(creator: Creator) {
print("Client: I'm not aware of the creator's class, but it still works.\n"
+ creator.someOperation());
}
// ...
}
/// لنرى الآن كيف سيعمل كل ذلك..
class FactoryMethodConceptual: XCTestCase {
func testFactoryMethodConceptual() {
/// يختار التطبيق نوع منشئ بناءً على الإعدادات أو البيئة.
print("App: Launched with the ConcreteCreator1.");
Client.someClientCode(creator: ConcreteCreator1())
print("\nApp: Launched with the ConcreteCreator2.");
Client.someClientCode(creator: ConcreteCreator2())
}
}
Output.txt: نتائج التنفيذ
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
مثال واقعي
Example.swift: مثال واقعي
import XCTest
class FactoryMethodRealWorld: XCTestCase {
func testFactoryMethodRealWorld() {
let info = "Very important info of the presentation"
let clientCode = ClientCode()
/// اعرض المعلومات باستخدام الواي فاي.
clientCode.present(info: info, with: WifiFactory())
/// اعرض المعلومات باستخدام البلوتوث.
clientCode.present(info: info, with: BluetoothFactory())
}
}
protocol ProjectorFactory {
func createProjector() -> Projector
func syncedProjector(with projector: Projector) -> Projector
}
extension ProjectorFactory {
/// ProjectorFactory التطبيق الأساسي لـ
func syncedProjector(with projector: Projector) -> Projector {
/// خاص بها Projector تنشئ كل نسخة جهاز عرض خاص.
let newProjector = createProjector()
/// زامِن أجهزة العرض.
newProjector.sync(with: projector)
return newProjector
}
}
class WifiFactory: ProjectorFactory {
func createProjector() -> Projector {
return WifiProjector()
}
}
class BluetoothFactory: ProjectorFactory {
func createProjector() -> Projector {
return BluetoothProjector()
}
}
protocol Projector {
/// واجهة جهاز العرض المجردة.
var currentPage: Int { get }
func present(info: String)
func sync(with projector: Projector)
func update(with page: Int)
}
extension Projector {
/// الاستخدام الأساسي لأساليب أجهزة العرض.
func sync(with projector: Projector) {
projector.update(with: currentPage)
}
}
class WifiProjector: Projector {
var currentPage = 0
func present(info: String) {
print("Info is presented over Wifi: \(info)")
}
func update(with page: Int) {
/// ... مرر الصفحة باستخدام اتصال الواي فاي.
/// ...
currentPage = page
}
}
class BluetoothProjector: Projector {
var currentPage = 0
func present(info: String) {
print("Info is presented over Bluetooth: \(info)")
}
func update(with page: Int) {
/// ... مرر الصفحة باستخدام اتصال البلوتوث.
/// ...
currentPage = page
}
}
private class ClientCode {
private var currentProjector: Projector?
func present(info: String, with factory: ProjectorFactory) {
/// تفقد إن كان العميل يعرض شيئًا بالفعل ...
guard let projector = currentProjector else {
/// فارغ، أنشئ جهاز عرض جديد وابدأ العرض 'currentProjector' متغير.
let projector = factory.createProjector()
projector.present(info: info)
self.currentProjector = projector
return
}
/// شيفرة العميل لديها جهاز عرض بالفعل، سنزامن صفحات جهاز العرض القديم
/// مع الجهاز الجديد.
self.currentProjector = factory.syncedProjector(with: projector)
self.currentProjector?.present(info: info)
}
}
Output.txt: نتائج التنفيذ
Info is presented over Wifi: Very important info of the presentation
Info is presented over Bluetooth: Very important info of the presentation
الاستخدام في لغة TypeScript
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يستخدم أسلوب المصنع بكثرة في شيفرة TypeScript، وهو مفيد في كتابة شيفرة عالية المرونة.
كما تقدم في شرح الاستخدام في لغة جافا، يمكن ملاحظة أساليب المصنع من الأساليب الإنشائية التي تنشئ كائنات من فئات حقيقية (concrete classes) لكنها تعيدها ككائنات من نوع مجرد (abstract type) أو واجهة مجردة (abstract interface).
مثال: بُنية النمط
يوضح هذاالمثال بنية نمط أسلوب المصنع، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.ts: مثال تصوري
/**
* تصرح فئة المنشئ عن أسلوب المصنع الذي يجب أن يعيد كائنًا من فئة أحد المنتجات
* وتوضح الفئة الفرعية للمنشئ في الغالب استخدامات هذا الأسلوب.
*/
abstract class Creator {
/**
* لاحظ أن المنشئ قد يوضح بعض الاستخدامات الافتراضية لأسلوب المصنع.
*/
public abstract factoryMethod(): Product;
/**
* لاحظ كذلك أن وظيفة المنشئ الأساسية ليست إنشاء المنتجات، رغم مايوحي به الاسم،
* لكن له عادة منطق عمل يعتمد على كائنات المنتج المعادة
* بواسطة أسلوب المصنع.
* وتستطيع الفئات الفرعية تغيير منطق العمل ذاك بشكل غير مباشر
* عبر تخطي أسلوب المصنع وإعادة نوع مختلف من المنتجات منه.
*/
public someOperation(): string {
// استدع أسلوب المصنع لإنشاء كائن منتج.
const product = this.factoryMethod();
// والآن، استخدم المنتج.
return `Creator: The same creator's code has just worked with ${product.operation()}`;
}
}
/**
* تتخطى المنشئات الحقيقية أسلوب المصنع من أجل تغيير نوع المنتج.
*/
class ConcreteCreator1 extends Creator {
/**
* لاحظ أن توقيع الأسلوب لا يزال يستخدم نوع المنتج النظري برغم
* أن المنتج الحقيقي مُعاد فعلًا من الأسلوب، وبهذه الطريقة يظل
* المنشئ مستقلًا من فئات المنتج الحقيقي.
*/
public factoryMethod(): Product {
return new ConcreteProduct1();
}
}
class ConcreteCreator2 extends Creator {
public factoryMethod(): Product {
return new ConcreteProduct2();
}
}
/**
* تصرح واجهة المنتج عن العمليات التي يجب أن تستخدمها المنتجات الحقيقية.
*/
interface Product {
operation(): string;
}
/**
* توضح المنتجات الحقيقية الاستخدامات المختلفة لواجهة المنتج.
*/
class ConcreteProduct1 implements Product {
public operation(): string {
return '{Result of the ConcreteProduct1}';
}
}
class ConcreteProduct2 implements Product {
public operation(): string {
return '{Result of the ConcreteProduct2}';
}
}
/**
* تعمل شيفرة العميل مع نسخة من منشئ حقيقي ولو من خلال واجهته
* الأساسية، فطالما أن العميل يظل عاملًا مع المنشئ من خلال
* واجهة أساسية فيمكنك تمرير أي فئة منشئ فرعية إليه.
*/
function clientCode(creator: Creator) {
// ...
console.log('Client: I\'m not aware of the creator\'s class, but it still works.');
console.log(creator.someOperation());
// ...
}
/**
* يختار التطبيق نوع منشئ بناءً على الإعدادات أو البيئة.
*/
console.log('App: Launched with the ConcreteCreator1.');
clientCode(new ConcreteCreator1());
console.log('');
console.log('App: Launched with the ConcreteCreator2.');
clientCode(new ConcreteCreator2());
Output.txt: نتائج التنفيذ
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}