شعار ConstructiCat
CodeBust.
تصفح القسم ▾

المصنع المجرد (Abstract Factory).

المصنع المجرد (Abstract Factory) هو نمط تصميم إنشائي يتيح لك إنتاج عائلات من الكائنات ذات الصلة دون تحديد فئاتها الملموسة.

##الغرض

المصنع المجرد (Abstract Factory) هو نمط تصميم إنشائي يتيح لك إنتاج عائلات من الكائنات ذات الصلة دون تحديد فئاتها الملموسة.

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

##المشكلة

تخيل أنك تقوم بإنشاء محاكي لمتجر أثاث. يتكون كودك من فئات تمثل:

  1. عائلة من المنتجات ذات الصلة، مثلاً: Chair + Sofa + CoffeeTable.

  2. عدة بدائل لهذه العائلة. على سبيل المثال، المنتجات Chair + Sofa + CoffeeTable متوفرة في هذه البدائل: Modern، Victorian، ArtDeco.

عائلات المنتجات وأنواعها المختلفة

عائلات المنتجات وبدائلها.

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

الأريكة ذات الطراز الحديث لا تتطابق مع الكراسي ذات الطراز الفيكتوري.

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

##الحل

أول شيء يقترحه نمط المصنع المجرد هو الإعلان صراحةً عن واجهات لكل منتج متميز في عائلة المنتجات (مثل الكرسي أو الأريكة أو طاولة القهوة). بعد ذلك، يمكنك جعل جميع بدائل المنتجات تتبع هذه الواجهات. على سبيل المثال، يمكن لجميع بدائل الكراسي تنفيذ واجهة Chair؛ ويمكن لجميع بدائل طاولة القهوة تنفيذ واجهة CoffeeTable، وهكذا.

«interface» Chair +hasLegs()+sitOn() VictorianChair ...+hasLegs()+sitOn() ModernChair ...+hasLegs()+sitOn()

الخطوة التالية هي التصريح عن المصنع المجرد—واجهة تحتوي على قائمة بدوال الإنشاء لجميع المنتجات التي تعد جزءاً من عائلة المنتجات (على سبيل المثال، createChair و createSofa و createCoffeeTable). يجب أن ترجع هذه الدوال أنواع منتجات مجردة تمثلها الواجهات التي استخرجناها سابقاً: Chair، Sofa، CoffeeTable وما إلى ذلك.

«interface» FurnitureFactory +createChair() Chair+createCoffeeTable() CoffeeTable+createSofa() Sofa VictorianFurnitureFactory ...+createChair() Chair+createCoffeeTable() CoffeeTable+createSofa() Sofa ModernFurnitureFactory ...+createChair() Chair+createCoffeeTable() CoffeeTable+createSofa() Sofa

الآن، ماذا عن بدائل المنتج؟ لكل بديل من عائلة المنتجات، نقوم بإنشاء فئة مصنع منفصلة بناءً على واجهة AbstractFactory. المصنع هو فئة تعيد منتجات من نوع معين. على سبيل المثال، يمكن لـ ModernFurnitureFactory فقط إنشاء كائنات ModernChair و ModernSofa و ModernCoffeeTable.

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

لا ينبغي للعميل أن يهتم بالفئة الملموسة للمصنع الذي يعمل معه.

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

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

##البنية

«interface» AbstractFactory +createProductA() : ProductA+createProductB() : ProductB ConcreteFactory1 ...+createProductA() : ProductA+createProductB() : ProductB ConcreteFactory2 ...+createProductA() : ProductA+createProductB() : ProductB Client -factory : AbstractFactory+Client(f : AbstractFactory)+someOperation() AbstractProductA AbstractProductB ConcreteProductA1 ConcreteProductB1 ConcreteProductA2 ConcreteProductB2 return new ConcreteProductA2() ProductA pa = factory.createProductA()
«interface» AbstractFactory +createProductA() : ProductA+createProductB() : ProductB ConcreteFactory1 ...+createProductA() : ProductA+createProductB() : ProductB ConcreteFactory2 ...+createProductA() : ProductA+createProductB() : ProductB Client -factory : AbstractFactory+Client(f : AbstractFactory)+someOperation() AbstractProductA AbstractProductB ConcreteProductA1 ConcreteProductB1 ConcreteProductA2 ConcreteProductB2 return new ConcreteProductA2() ProductA pa = factory.createProductA() 4 2 5 3 1 2 4
  1. تعلن المنتجات المجردة (Abstract Products) عن واجهات لمجموعة من المنتجات المتميزة ولكن ذات الصلة والتي تشكل عائلة منتجات.

  2. المنتجات الملموسة (Concrete Products) هي تطبيقات مختلفة للمنتجات المجردة، مجمعة حسب البدائل. يجب تنفيذ كل منتج مجرد (كرسي/أريكة) في جميع البدائل المعطاة (فيكتوري/حديث).

  3. تعلن واجهة المصنع المجرد (Abstract Factory) عن مجموعة من الدوال لإنشاء كل من المنتجات المجردة.

  4. تقوم المصانع الملموسة (Concrete Factories) بتنفيذ دوال الإنشاء للمصنع المجرد. يتوافق كل مصنع ملموس مع بديل معين من المنتجات ويخلق فقط تلك البدائل للمنتجات.

  5. على الرغم من أن المصانع الملموسة تنشئ منتجات ملموسة، إلا أن تواقيع دوال الإنشاء الخاصة بها يجب أن ترجع المنتجات المجردة المقابلة. بهذه الطريقة لا يرتبط كود العميل الذي يستخدم المصنع بالبديل المحدد للمنتج الذي يحصل عليه من المصنع. يمكن لـ العميل (Client) العمل مع أي مصنع ملموس/بديل منتج، طالما أنه يتواصل مع كائناتها عبر الواجهات المجردة.

##الكود الزائف

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

«interface» GUIFactory +createButton() Button+createCheckbox() Checkbox WinFactory ...+createButton() Button+createCheckbox() Checkbox MacFactory ...+createButton() Button+createCheckbox() Checkbox Button Checkbox WinButton WinCheckbox MacButton MacCheckbox Application -factory: GUIFactory-button: Button...+Application(f: GUIFactory)+createUI()+paint()

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

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

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

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

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

// تعلن واجهة المصنع المجرد عن مجموعة من الدوال التي
// تعيد منتجات مجردة مختلفة. تسمى هذه المنتجات عائلة
// وترتبط بموضوع أو مفهوم عالي المستوى. عادةً ما تكون منتجات
// عائلة واحدة قادرة على التعاون فيما بينها. قد تحتوي عائلة
// المنتجات على عدة بدائل، ولكن منتجات بديل واحد غير متوافقة
// مع منتجات بديل آخر.
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()


// يجب أن يكون لكل منتج متميز من عائلة منتجات واجهة
// أساسية. يجب أن تنفذ جميع بدائل المنتج هذه الواجهة.
interface Button is
    method paint()

// يتم إنشاء المنتجات الملموسة بواسطة المصانع الملموسة المقابلة.
class WinButton implements Button is
    method paint() is
        // تقديم زر بنمط Windows.

class MacButton implements Button is
    method paint() is
        // تقديم زر بنمط macOS.

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

class WinCheckbox implements Checkbox is
    method paint() is
        // تقديم خانة اختيار بنمط Windows.

class MacCheckbox implements Checkbox is
    method paint() is
        // تقديم خانة اختيار بنمط macOS.


// يعمل كود العميل مع المصانع والمنتجات فقط من خلال
// الأنواع المجردة: GUIFactory و Button و Checkbox. يتيح
// هذا تمرير أي فئة فرعية من المصنع أو المنتج إلى كود العميل دون كسرها.
class Application is
    private field factory: GUIFactory
    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 == "Mac") then
            factory = new MacFactory()
        else
            throw new Exception("Error! Unknown operating system.")

        Application app = new Application(factory)

##متى يُستخدم

استخدم نمط المصنع المجرد (Abstract Factory) عندما يحتاج كودك إلى العمل مع عائلات مختلفة من المنتجات ذات الصلة، ولكنك لا تريده أن يعتمد على الفئات الملموسة لهذه المنتجات—فقد تكون غير معروفة مسبقاً أو قد ترغب ببساطة في السماح بالتوسيع مستقبلاً.

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

فكر في تطبيق المصنع المجرد عندما يكون لديك فئة تحتوي على مجموعة من دوال المصنع (Factory Methods) التي تشوش على مسؤوليتها الأساسية.

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

##كيفية التنفيذ

  1. قم برسم مصفوفة توضح أنواع المنتجات المتميزة مقابل بدائل هذه المنتجات.

  2. قم بالتصريح عن واجهات المنتجات المجردة لجميع أنواع المنتجات. ثم اجعل جميع فئات المنتجات الملموسة تنفذ هذه الواجهات.

  3. قم بالتصريح عن واجهة المصنع المجرد مع مجموعة من دوال الإنشاء لجميع المنتجات المجردة.

  4. قم بتنفيذ مجموعة من فئات المصنع الملموسة، واحدة لكل بديل من بدائل المنتج.

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

  6. ابحث في الكود عن جميع الاستدعاءات المباشرة لمنشئات المنتجات. واستبدلها باستدعاءات لدالة الإنشاء المناسبة في كائن المصنع.

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

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

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

  • تبدأ العديد من التصميمات باستخدام Factory Method (أقل تعقيداً وأكثر قابلية للتخصيص عبر الفئات الفرعية) وتتطور نحو Abstract Factory، أو Prototype، أو Builder (أكثر مرونة، ولكنها أكثر تعقيداً).

  • Builder يركز على إنشاء كائنات معقدة خطوة بخطوة. Abstract Factory يتخصص في إنشاء عائلات من الكائنات ذات الصلة. يعيد _Abstract Factory_ المنتج على الفور، بينما يسمح لك _Builder_ بتشغيل بعض خطوات البناء الإضافية قبل جلب المنتج.

  • غالباً ما تعتمد فئات Abstract Factory على مجموعة من Factory Methods، ولكن يمكنك أيضاً استخدام Prototype لتركيب الدوال على هذه فئات.

  • Abstract Factory يمكن أن يعمل كبديل لـ Facade عندما تريد فقط إخفاء طريقة إنشاء كائنات النظام الفرعي عن كود العميل.

  • يمكنك استخدام Abstract Factory مع Bridge. هذا الاقتران مفيد عندما لا تعمل بعض التجريدات التي يحددها _الجسر_ إلا مع تطبيقات محددة. في هذه الحالة، يمكن لـ _المصنع المجرد_ تغليف هذه العلاقات وإخفاء التعقيد عن كود العميل.

  • يمكن تنفيذ كل من Abstract Factories و Builders و Prototypes كـ Singletons.

##محتوى إضافي

  • اقرأ مقارنة المصانع لمعرفة المزيد عن الاختلافات بين أنماط ومفاهيم المصانع المختلفة.