نمط الباني Builder

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث

نمط الباني (Builder) هو نمط تصميم إنشائي يسمح لك ببناء كائنات معقدة خطوة خطوة، كما يمكنك من إنتاج أنواعًا وتمثيلات (representations) مختلفة من الكائن باستخدام نفس شيفرة البناء (Construction code).

المشكلة

إن كان لدينا كائن معقد يتطلب تهيئة مرهقة وتفصيلية للكثير من الحقول والكائنات المتداخلة (nested objects)، فإن شيفرة التهيئة اللازمة لذلك الكائن تُدفن عادة داخل منشئ هائل الحجم به معامِلات (parameters) كثيرة، أو أسوأ من ذلك، أن تتناثر تلك الشيفرة في أرجاء شيفرة العميل.

dpbld.problem1.png

(ش.1) قد تجعل البرنامج معقدًا للغاية إن أنشأت فئة فرعية لكل تهيئة محتملة للكائن.

فمثلًا، دعنا ننظر في كيفية إنشاء كائن منزل House: نحتاج أولًا إلى بناء أربع جدران وأرضية، وتركيب الباب، ونافذتين، وكذلك بناء السقف. لكن إن أردت منزلًا أكبر مع فناء خلفي وأنظمة تدفئة وسباكة وكهرباء وغير ذلك، فإن الحل الأبسط لذلك هو توسيع فئة House الأساسية وإنشاء مجموعة من الفئات الفرعية لتغطية جميع تجميعات (combinations) المعامِلات، لكن سيكون لديك في النهاية عدد كبير من الفئات الفرعية، وستزيد الشيفرة والهرمية عند إضافة أي معامِل جديد، مثل إضافة رواق للمنزل.

هناك منظور آخر لا يحتاج إضافة الفئات الفرعية، وهو إنشاء منشئ ضخم داخل فئة House مع كل المعامِلات المحتملة التي تتحكم في كائن المنزل، وسيلغي هذا الحلُ الحاجةَ إلى الفئات الفرعية، لكنه سيحدث مشكلة أخرى.

dpbld.problem2.png (ش.2) المنشئ الذي فيه معامِلات كثيرة مشكلته أن كل المعامِلات مطلوبة دائمًا.

ستكون أكثر المعامِلات غير مستخدمة في أغلب الوقت، مما يجعل المنشئ كبيرًا بدون داعي، فمثلًا قد تحتاج نسبة قليلة من المنازل حمامًا للسباحة، لذا ستكون المعامِلات المطلوبة لحمام السباحة غير مستخدمة في 90% من الوقت مثلًا.

الحل

يقترح نمط الباني (Builder) أن تستخرج شيفرة بناء الكائن من فئته وتنقلها إلى كائنات أخرى تسمى builders.

dpbld.solution1.png (ش.3) يسمح لك نمط الباني ببناء كائنات معقدة خطوة بخطوة، ولا يسمح في نفس الوقت للكائنات الأخرى بالوصول إلى المنتج أثناء بنائه.

ينظم النمط بناء الكائن في مجموعة خطوات (buildWalls, buildDoor، إلخ)، وستنفذ سلسلة من تلك الخطوات على كائن باني من أجل إنشاء كائن ما، والمهم هنا أنك لا تحتاج إلى استدعاء جميع الخطوات، بل تستدعي تلك المطلوبة لإنتاج تجهيزات معينة للكائن.

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

dpbld.builder-comic-1-en.png

(ش.4) تنفذ كائنات البناء المختلفة نفس المهام بطرق مختلفة.

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

الموجِّه Director

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


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

البنية

dpbld.structure-indexed.png

  1. تصرح واجهة الباني (Builder) عن خطوات إنشاء المنتج المشتركة بين كل أنواع كائنات البناء.
  2. توفر كائنات البناء الحقيقية (Concrete Builders) استخدامات مختلفة لخطوات الإنشاء، وقد تنتج منتجات لا تتبع الواجهة المشتركة.
  3. المنتجات (Products) هي كائنات ناتجة تُبنى بواسطة كائنات بناء مختلفة لا تنتمي بالضرورة إلى نفس الهرمية الفئوية أو إلى نفس الواجهة.
  4. تحدد فئة الموجه Director الترتيب الذي يجب أن تُستدعى به خطوات الإنشاء كي تستطيع إنشاء وإعادة استخدام إعدادات بعينها من المنتجات.
  5. يجب أن يربط العميل Client أحد كائنات البناء مع الموجّه، عادة يتم الأمر مرة واحدة فقط من خلال معامِلات منشئ الموجّه (Director's Constructor)، ثم يستخدم الموجه كائن البناء ذاك في كل الإنشاءات التالية، لكن هناك طريقة أخرى حين يمرِّر العميل كائن البناء إلى أسلوب الإنتاج الخاص بالموجّه، فعندئذ تستطيع استخدام كائن بناء مختلف في كل مرة تنتج فيها شيئًا بواسطة الموجّه.

مثال توضيحي

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

dpbld.example.png

(ش.6) مثال تفصيلي لبناء سيارات وأدلة استخدام تناسب موديلات تلك السيارات.

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

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

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

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

class Manual is
    // يجب أن تحتوي كل سيارة على دليل استخدام متوافق مع
    // إعدادات السيارة ويصف كل مزاياها.


// تحدد واجهة الباني أساليب إنشاء الأجزاء المختلفة من 
// كائنات المنتج.
interface Builder is
    method reset()
    method setSeats(...)
    method setEngine(...)
    method setTripComputer(...)
    method setGPS(...)

// تتبع فئات الباني الحقيقي واجهة الباني وتوفر استخدامات
// محددة لخطوات البناء، وقد يحتوي برنامجك على صور متعددة
// للبانيات، تُطبق كل واحدة منها بشكل مختلف.
class CarBuilder implements Builder is
    private field car:Car
    // يجب أن تحتوي نسخة الباني الحديثة على كائن منتج فارغ
    // تستخدمه لاحقًا أثناء التجميع.
    constructor CarBuilder() is
        this.reset()

    // الكائن الذي يتم بناؤه reset ينظف أسلوب.
    method reset() is
        this.car = new Car()

    // تعمل جميع خطوات الإنتاج مع نفس نسخة المنتج.
    method setSeats(...) is
        //  ضع عدد المقاعد في السيارة.

    method setEngine(...) is
        // ركِّب المحرك.

    method setTripComputer(...) is
        // ركِّب حاسوب الرحلات.

    method setGPS(...) is
        // GPS ركب جهاز الملاحة.

    // يفترض بالبانيات الحقيقية أن توفر أساليبها الخاصة
    // بجلب النتائج، لأن الأنواع المختلفة من البانيات قد تنشئ
    // منتجات مختلفة تمامًا لا تتبع نفس الواجهة، لهذا لا يمكن
    // التصريح عن أساليب كتلك في واجهة الباني، على الأقل
    // statically-typed language في لغات البرمجة من النوع الساكن.
    // ينبغي أن تكون نسخة الباني جاهزة لبدء إنتاج منتج جديد بعد
    // إعادة النتيجة النهائية إلى العميل. لذا من المعتاد استدعاء أسلوب 
    // getProduct إعادة الضبط في نهاية متن أسلوب.
    // لكن هذا السلوك ليس ضروريًا، ويمكنك جعل الباني الخاص بك ينتظر
    // استدعاء إعادة ضبط صريح من شيفرة العميل قبل التخلص من النتيجة
    // السابقة.
    method getProduct():Car is
        product = this.car
        this.reset()
        return product

// يسمح لك الباني بإنشاء منتجات لا تتبع الواجهة المشتركة
// على عكي الأنماط الإنشائية الأخرى.
class CarManualBuilder implements Builder is
    private field manual:Manual

    constructor CarManualBuilder() is
        this.reset()

    method reset() is
        this.manual = new Manual()

    method setSeats(...) is
        // وثِّق مزايا مقعد السيارة.

    method setEngine(...) is
        // أضف إرشادات المحرك.

    method setTripComputer(...) is
        // أضف إرشادات حاسوب الرحلات.
    method setGPS(...) is
        // Add GPS instructions.

    method getProduct():Manual is
        // أعد الدليل وأعد ضبط الباني.


// لا يكون الموجّه مسؤولًا إلا عن تنفيذ خطوات البناء بتسلسل معين.
// ذلك مفيد عندما تنتج منتجات وفقًا لترتيب أو إعدادات معينة.
// وتُعد فئةالباني خيارية بما أن العميل يستطيع التحكم في
// كائنات البناء مباشرة.
class Director is
    private field builder:Builder

    // يعمل الموجّه مع أي نسخة للباني تمررها شيفرة العميل 
    // إليه، وبهذه الطريقة قد تغير النوع النهائي للمنتج
    // الذي يتم تجميعه.
    method setBuilder(builder:Builder)
        this.builder = builder

    // يستطيع الموجّه إنشاء صور عدة للمنتج باستخدام
    // نفس خطوات البناء.
    method constructSportsCar(builder: Builder) is
        builder.reset()
        builder.setSeats(2)
        builder.setEngine(new SportEngine())
        builder.setTripComputer(true)
        builder.setGPS(true)

    method constructSUV(builder: Builder) is
        // ...


// وتمررها إلى الموجّه ثم تبدأ builder تنشئ شيفرة العميل كائن بانيًا
// عملية الإنشاء، وتُجلب النتيجة النهائية من الكائن الباني.
class Application is

    method makeCar() is
        director = new Director()

        CarBuilder builder = new CarBuilder()
        director.constructSportsCar(builder)
        Car car = builder.getProduct()

        CarManualBuilder builder = new CarManualBuilder()
        director.constructSportsCar(builder)

        // يُجلب المنتج النهائي من كائنٍ باني بما أن الموجّه
        // لا يكون على علم به، ولا يعتمد على بانيات حقيقية
        // أو منتجات حقيقية.
        Manual manual = builder.getProduct()

قابلية التطبيق

  • استخدم نمط الباني للتخلص من المنشئ التليسكوبي (Telescopic Constructor).

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

class Pizza {
    Pizza(int size) { ... }
    Pizza(int size, boolean cheese) { ... }
    Pizza(int size, boolean cheese, boolean pepperoni) { ... }
    // ...

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

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

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

  • استخدم الباني لإنشاء أشجار نمط المركَّب أو أي كائنات معقدة أخرى.

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

كيفية الاستخدام

  • تأكد أنك تحدد بوضوح خطوات الإنشاء لكل أشكال المنتج المتاحة، وإلا فلن تستطيع المضي قدمًا في تطبيق النمط.
  • صرح عن هذه الخطوات في واجهة الباني المشتركة.
  • أنشئ فئة بنّاء حقيقية لكل شكل من أشكال المنتج وطبق خطوات الإنشاء الخاصة به. لا تنسى تطبيق أسلوب لجلب نتيجة البناء، لا يمكن التصريح عن هذا الأسلوب داخل واجهة الباني بسبب أن البانيات المختلفة قد تبني منتجات ليس لها واجهة مشتركة، لهذا فإنك لا تعرف نوع الإعادة لمثل ذلك الأسلوب، لكن بأي حال، فإن كنت تتعامل مع منتجات من هرمية واحدة، فيمكن إضافة أسلوب الجلب Fetching Method إلى الواجهة المشتركة.
  • فكر في إنشاء فئة موجّهDirector Class، ستغلف تلك الفئة طرقًا مختلفة لبناء منتج باستخدام نفس كائن الباني.
  • تنشئ شيفرة العميل كائنات الباني والموجّه، ويجب أن يمرِّر العميل كائنًا بانيًا إلى الموجِّه قبل بدء الإنشاء، وعادة ما يفعل العميل ذلك مرة واحدة فقط من خلال معامِلات منشئ الموجّه (Parmeters of the Director's Constructor)، ويستخدم الموجه كائن البنّاء في كل عمليات الإنشاء التالية، يوجد منظور مختلف لهذا يُمرَّر فيه البنّاء مباشرة إلى أسلوب الإنشاء للموجّه.
  • يمكن الحصول على نتيجة الإنشاء مباشرة من الموجّه إن كانت كل المنتجات تتبع نفس الواجهة، وإلا فإن العميل يجب أن يجلب النتيجة من البنّاء.

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

المزايا

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

العيوب

  • يزيد التعقيد الكلي للشيفرة بما أن النمط يتطلب إنشاء عدة فئات جديدة.

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

  • تستخدم تصميمات كثيرة أسلوب المصنع (Factory Method) بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص عبر الفئات الفرعية، ثم تنتقل إلى أسلوب المصنع المجرد أو النموذج الأولي (prototype) أو الباني (Builder)، حيث أنهم أكثر مرونة لكن أكثر تعقيدًا في المقابل.
  • يركز نمط الباني (Builder) على إنشاء كائنات معقدة خطوة بخطوة، أما المصنع المجرد فيتخصص في إنشاء عائلات من المنتجات المتعلقة ببعضها، كذلك يعيد المنتج فورًا في حين أن الباني يسمح لك بإجراء بعض خطوات الإنشاء الإضافية قبل الحصول على المنتج.
  • تستطيع استخدام نمط الباني أثناء إنشاء أشجار معقدة لنمط المركّب إذ يمكنك برمجة خطوات إنشائه لتعمل بشكل عكسي.
  • تستطيع دمج نمطي الباني والجسر، إذ تلعب فئة الموجّه دور التجريد (abstraction) بينما تتصرف بانيات مختلفة كتطبيقات (implementations).
  • يمكن استخدام المصانع المجردة والبانيات (Builders) والنماذج الأولية كمفردات Singletons.

الاستخدام في لغة جافا

المستوى: ★ ★ ☆

الانتشار: ★ ★ ★

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

يمكن ملاحظة النمط داخل الفئة التي لديها أسلوب إنشاء وحيد وعدة أساليب لتهيئة الكائن الناتج، وتدعم أساليب الباني التسلسل (Chaining) في الغالب. (مثال: باني->ضع قيمةأ(1)->ضع قيمةب(2)->أنشئ() ).

إنشاء سيارة خطوة بخطوة

يسمح لك نمط الباني في هذا المثال بإنشاء طرازات مختلفة من سيارة ما، ويوضح كيف يُخرج الباني منتجات من أنواع مختلفة (دليل السيارة) باستخدام نفس خطوات الإنشاء.

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

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

البنّاؤون Builders

 builders/Builder.java: واجهة الباني المشتركة
package refactoring_guru.builder.example.builders;

import refactoring_guru.builder.example.cars.Type;
import refactoring_guru.builder.example.components.Engine;
import refactoring_guru.builder.example.components.GPSNavigator;
import refactoring_guru.builder.example.components.Transmission;
import refactoring_guru.builder.example.components.TripComputer;

/**
 * تحدد واجهة الباني كل الطرق المحتملة لتهيئة منتج ما.
 */
public interface Builder {
    void setType(Type type);
    void setSeats(int seats);
    void setEngine(Engine engine);
    void setTransmission(Transmission transmission);
    void setTripComputer(TripComputer tripComputer);
    void setGPSNavigator(GPSNavigator gpsNavigator);
}
 builders/CarBuilder.java: باني السيارة (Builder of the car)
package refactoring_guru.builder.example.builders;

import refactoring_guru.builder.example.cars.Car;
import refactoring_guru.builder.example.cars.Type;
import refactoring_guru.builder.example.components.Engine;
import refactoring_guru.builder.example.components.GPSNavigator;
import refactoring_guru.builder.example.components.Transmission;
import refactoring_guru.builder.example.components.TripComputer;

/**
 * تستخدم البانيات الحقيقية خطوات محددة في الواجهة المشتركة.
 */
public class CarBuilder implements Builder {
    private Type type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    @Override
    public void setType(Type type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    public Car getResult() {
        return new Car(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}
 builders/CarManualBuilder.java: باني دليل السيارة (Builder of the car manual)
package refactoring_guru.builder.example.builders;

import refactoring_guru.builder.example.cars.Manual;
import refactoring_guru.builder.example.cars.Type;
import refactoring_guru.builder.example.components.Engine;
import refactoring_guru.builder.example.components.GPSNavigator;
import refactoring_guru.builder.example.components.Transmission;
import refactoring_guru.builder.example.components.TripComputer;

/**
 * يستطيع الباني إنشاء منتجات لا علاقة لها ببعضها، على عكس أنماط التصميم
 * الإنشائية الأخرى التي ليس لها واجهة مشتركة.
 *
 * يوضح هذا المثال كيفية بناء دليل استخدام لسيارة باستخدام نفس خطوات
 * بناء السيارة نفسها، هذا يسمح بإنتاج أدلة لطرازات محددة من السيارات
 * المجهزة بمزايا مختلفة.
 */
public class CarManualBuilder implements Builder{
    private Type type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    @Override
    public void setType(Type type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    public Manual getResult() {
        return new Manual(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}

السيارات Cars

 cars/Car.java: منتج السيارة
package refactoring_guru.builder.example.cars;

import refactoring_guru.builder.example.components.Engine;
import refactoring_guru.builder.example.components.GPSNavigator;
import refactoring_guru.builder.example.components.Transmission;
import refactoring_guru.builder.example.components.TripComputer;

/**
 * السيارة تمثل فئة منتج.
 */
public class Car {
    private final Type type;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;
    private double fuel = 0;

    public Car(Type type, int seats, Engine engine, Transmission transmission,
               TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.type = type;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        this.tripComputer.setCar(this);
        this.gpsNavigator = gpsNavigator;
    }

    public Type getType() {
        return type;
    }

    public double getFuel() {
        return fuel;
    }

    public void setFuel(double fuel) {
        this.fuel = fuel;
    }

    public int getSeats() {
        return seats;
    }

    public Engine getEngine() {
        return engine;
    }

    public Transmission getTransmission() {
        return transmission;
    }

    public TripComputer getTripComputer() {
        return tripComputer;
    }

    public GPSNavigator getGpsNavigator() {
        return gpsNavigator;
    }
}
 cars/Manual.java: منتج دليل الاستخدام
package refactoring_guru.builder.example.cars;

import refactoring_guru.builder.example.components.Engine;
import refactoring_guru.builder.example.components.GPSNavigator;
import refactoring_guru.builder.example.components.Transmission;
import refactoring_guru.builder.example.components.TripComputer;

/**
 * دليل استخدام السيارة هو منتج آخر، ولاحظ أنه لا يشترك مع السيارة
 * في الأصل، أي أنهما لا يرتبطان ببعضهما.
 */
public class Manual {
    private final Type type;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;

    public Manual(Type type, int seats, Engine engine, Transmission transmission,
                  TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.type = type;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        this.gpsNavigator = gpsNavigator;
    }

    public String print() {
        String info = "";
        info += "Type of car: " + type + "\n";
        info += "Count of seats: " + seats + "\n";
        info += "Engine: volume - " + engine.getVolume() + "; mileage - " + engine.getMileage() + "\n";
        info += "Transmission: " + transmission + "\n";
        if (this.tripComputer != null) {
            info += "Trip Computer: Functional" + "\n";
        } else {
            info += "Trip Computer: N/A" + "\n";
        }
        if (this.gpsNavigator != null) {
            info += "GPS Navigator: Functional" + "\n";
        } else {
            info += "GPS Navigator: N/A" + "\n";
        }
        return info;
    }
}
 cars/Type.java
package refactoring_guru.builder.example.cars;

public enum Type {
    CITY_CAR, SPORTS_CAR, SUV
}

العناصر Components

 components/Engine.java: ميزة 1 للمنتج
package refactoring_guru.builder.example.components;

/**
 * مثال على ميزة لسيارة ما.
 */
public class Engine {
    private final double volume;
    private double mileage;
    private boolean started;

    public Engine(double volume, double mileage) {
        this.volume = volume;
        this.mileage = mileage;
    }

    public void on() {
        started = true;
    }

    public void off() {
        started = false;
    }

    public boolean isStarted() {
        return started;
    }

    public void go(double mileage) {
        if (started) {
            this.mileage += mileage;
        } else {
            System.err.println("Cannot go(), you must start engine first!");
        }
    }

    public double getVolume() {
        return volume;
    }

    public double getMileage() {
        return mileage;
    }
}
 components/GPSNavigator.java: ميزة 2 للمنتج
package refactoring_guru.builder.example.components;

/**
 * مثال على ميزة أخرى للسيارة.
 */
public class GPSNavigator {
    private String route;

    public GPSNavigator() {
        this.route = "221b, Baker Street, London  to Scotland Yard, 8-10 Broadway, London";
    }

    public GPSNavigator(String manualRoute) {
        this.route = manualRoute;
    }

    public String getRoute() {
        return route;
    }
}
 components/Transmission.java: ميزة 3 للمنتج
package refactoring_guru.builder.example.components;

/**
 * مثال على ميزة أخرى للسيارة.
 */
public enum Transmission {
    SINGLE_SPEED, MANUAL, AUTOMATIC, SEMI_AUTOMATIC
}
 components/TripComputer.java: ميزة 4 للمنتج
package refactoring_guru.builder.example.components;

import refactoring_guru.builder.example.cars.Car;

/**
 * مثال على ميزة أخرى للسيارة.
 */
public class TripComputer {

    private Car car;

    public void setCar(Car car) {
        this.car = car;
    }

    public void showFuelLevel() {
        System.out.println("Fuel level: " + car.getFuel());
    }

    public void showStatus() {
        if (this.car.getEngine().isStarted()) {
            System.out.println("Car is started");
        } else {
            System.out.println("Car isn't started");
        }
    }
}

الموجّه Director

 director/Director.java: الموجّه يتحكم بكائنات البناء
package refactoring_guru.builder.example.director;

import refactoring_guru.builder.example.builders.Builder;
import refactoring_guru.builder.example.cars.Type;
import refactoring_guru.builder.example.components.Engine;
import refactoring_guru.builder.example.components.GPSNavigator;
import refactoring_guru.builder.example.components.Transmission;
import refactoring_guru.builder.example.components.TripComputer;

/**
 * يحدد الموجِّه ترتيب خطوات البناء، ويعمل مع الكائن الباني من خلال
 * واجهة الباني المشتركة، لهذا قد لا يعرف ما المنتج الذي يتم بناؤه.
 */
public class Director {

    public void constructSportsCar(Builder builder) {
        builder.setType(Type.SPORTS_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(3.0, 0));
        builder.setTransmission(Transmission.SEMI_AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }

    public void constructCityCar(Builder builder) {
        builder.setType(Type.CITY_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(1.2, 0));
        builder.setTransmission(Transmission.AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }

    public void constructSUV(Builder builder) {
        builder.setType(Type.SUV);
        builder.setSeats(4);
        builder.setEngine(new Engine(2.5, 0));
        builder.setTransmission(Transmission.MANUAL);
        builder.setGPSNavigator(new GPSNavigator());
    }
}
 Demo.java: شيفرة العميل
package refactoring_guru.builder.example;

import refactoring_guru.builder.example.builders.CarBuilder;
import refactoring_guru.builder.example.builders.CarManualBuilder;
import refactoring_guru.builder.example.cars.Car;
import refactoring_guru.builder.example.cars.Manual;
import refactoring_guru.builder.example.director.Director;

/**
 * فئة العرض، يجتمع كل شيء هنا.
 */
public class Demo {

    public static void main(String[] args) {
        Director director = new Director();

        // -يحصل الموجه على كائن الباني الحقيقي من العميل -شيفرة التطبيق
        // ذلك لأن التطبيق يعرف أي كائن يجب استخدامه للحصول على منتج معين.
        CarBuilder builder = new CarBuilder();
        director.constructSportsCar(builder);

        // تستطيع الحصول على المنتج النهائي من الكائن الباني بما أن
        // الموجه لا يدرك بوجود البانيات الحقيقية ولا المنتجات ولا يعتمد
        // عليها.
        Car car = builder.getResult();
        System.out.println("Car built:\n" + car.getType());


        CarManualBuilder manualBuilder = new CarManualBuilder();

        // قد يعرف الموجّه عدة طرق للإنشاء.
        director.constructSportsCar(manualBuilder);
        Manual carManual = manualBuilder.getResult();
        System.out.println("\nCar manual built:\n" + carManual.print());
    }

}
 OutputDemo.txt: نتائج التنفيذ
Car built:
SPORTS_CAR

Car manual built:
Type of car: SPORTS_CAR
Count of seats: 2
Engine: volume - 3.0; mileage - 0.0
Transmission: SEMI_AUTOMATIC
Trip Computer: Functional
GPS Navigator: Functional

الاستخدام في لغة #C

المستوى: ★ ★ ☆

الانتشار: ★ ★ ★

أمثلة الاستخدام: ينتشر استخدام نمط الباني في لغة #C، إذ هو مفيد جدًا حين تريد إنشاء كائن به الكثير من خيارات التهيئة المحتملة.

يمكن ملاحظة النمط داخل الفئة التي لديها أسلوب إنشاء وحيد وعدة أساليب لتهيئة الكائن الناتج، وتدعم أساليب الباني التسلسل (Chaining) في الغالب. (مثال: باني->ضع قيمةأ(1)->ضع قيمةب(2)->أنشئ() ).

مثال تصوري

يوضح هذا المثال بنية نمط الباني Builder، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 Program.cs: مثال تصوري

using System;
using System.Collections.Generic;

namespace RefactoringGuru.DesignPatterns.Builder.Conceptual
{
    // تحدد واجهة الباني أساليب إنشاء الأجزاء المختلفة لكائنات المنتج.
    public interface IBuilder
    {
        void BuildPartA();
        
        void BuildPartB();
        
        void BuildPartC();
    }
    
    // تتبع فئات الباني الحقيقي واجهة الباني وتوفر تطبيقات محددة لخطوات
    // الإنشاء، وقد يحتوي برنامجك على عدة أشكال للبانيات تُطبق بأشكال مختلفة.
    public class ConcreteBuilder : IBuilder
    {
        private Product _product = new Product();
        // يجب أن تحتوي نسخة الباني الحديثة على كائن منتج فارغ يُستخدم
        // في عمليات التجميع التالية.
        public ConcreteBuilder()
        {
            this.Reset();
        }
        
        public void Reset()
        {
            this._product = new Product();
        }
        
        // تعمل كل خطوات الإنشاء مع نفس النسخة من المنتج.
        public void BuildPartA()
        {
            this._product.Add("PartA1");
        }
        
        public void BuildPartB()
        {
            this._product.Add("PartB1");
        }
        
        public void BuildPartC()
        {
            this._product.Add("PartC1");
        }
        
        // يفترض بالبانيات الحقيقية أن توفر أساليبها الخاصة
        // بجلب النتائج، لأن الأنواع المختلفة من البانيات قد تنشئ
        // منتجات مختلفة تمامًا لا تتبع نفس الواجهة، لهذا لا يمكن
        // التصريح عن أساليب كتلك في واجهة الباني، على الأقل
        // statically-typed language في لغات البرمجة من النوع الساكن.
        // ينبغي أن تكون نسخة الباني جاهزة لبدء إنتاج منتج جديد بعد
        // إعادة النتيجة النهائية إلى العميل. لذا من المعتاد استدعاء أسلوب 
        // getProduct إعادة الضبط في نهاية متن أسلوب.
        // لكن هذا السلوك ليس ضروريًا، ويمكنك جعل الباني الخاص بك ينتظر
        // استدعاء إعادة ضبط صريح من شيفرة العميل قبل التخلص من النتيجة
        // السابقة.
        public Product GetProduct()
        {
            Product result = this._product;

            this.Reset();

            return result;
        }
    }
    // من المنطقي استخدام نمط الباني فقط حين تكون منتجاتك بها الكثير
    // من التعقيدات وتتطلب تجهيزات كثيرة.
    // 
    // على عكس الأنماط الإنشائية الأخرى فإن البانيات الحقيقية المختلفة
    // تنتج منتجات غير مرتبطة ببعضها، أي ليس شرطًا أن تتبع نواتج
    // البانيات المختلفة نفس الواجهة دائمًا.
    public class Product
    {
        private List<object> _parts = new List<object>();
        
        public void Add(string part)
        {
            this._parts.Add(part);
        }
        
        public string ListParts()
        {
            string str = string.Empty;

            for (int i = 0; i < this._parts.Count; i++)
            {
                str += this._parts[i] + ", ";
            }

            str = str.Remove(str.Length - 2); // removing last ",c"

            return "Product parts: " + str + "\n";
        }
    }
    
    // لا يكون الموجّه مسؤولًا إلا عن تنفيذ خطوات البناء بتسلسل معين.
    // ذلك مفيد عندما تنتج منتجات وفقًا لترتيب أو إعدادات معينة.
    // وتُعد فئةالباني خيارية بما أن العميل يستطيع التحكم في
    // كائنات البناء مباشرة.
    public class Director
    {
        private IBuilder _builder;
        
        public IBuilder Builder
        {
            set { _builder = value; } 
        }
        
        // يستطيع الموجّه إنشاء صور عدة للمنتج باستخدام
        // نفس خطوات البناء.
        public void buildMinimalViableProduct()
        {
            this._builder.BuildPartA();
        }
        
        public void buildFullFeaturedProduct()
        {
            this._builder.BuildPartA();
            this._builder.BuildPartB();
            this._builder.BuildPartC();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // وتمررها إلى الموجّه builder تنشئ شيفرة العميل كائن بانيًا
            // ثم تبدأ عملية الإنشاء، وتُجلب النتيجة النهائية.
            // من الكائن الباني.
            var director = new Director();
            var builder = new ConcreteBuilder();
            director.Builder = builder;
            
            Console.WriteLine("Standard basic product:");
            director.buildMinimalViableProduct();
            Console.WriteLine(builder.GetProduct().ListParts());

            Console.WriteLine("Standard full featured product:");
            director.buildFullFeaturedProduct();
            Console.WriteLine(builder.GetProduct().ListParts());

            // تذكر أن نمط الباني يمكن استخدامه دون فئة الموجّه.
            Console.WriteLine("Custom product:");
            builder.BuildPartA();
            builder.BuildPartC();
            Console.Write(builder.GetProduct().ListParts());
        }
    }
}

 Output.txt: نتائج التنفيذ

Standard basic product:
Product parts: PartA1

Standard full featured product:
Product parts: PartA1, PartB1, PartC1

Custom product:
Product parts: PartA1, PartC1

الاستخدام في لغة PHP

المستوى: ★ ★ ☆

الانتشار: ★ ★ ★

أمثلة الاستخدام: ينتشر استخدام نمط الباني في لغة PHP، إذ هو مفيد جدًا حين تريد إنشاء كائن به الكثير من خيارات التهيئة المحتملة.

يمكن ملاحظة النمط داخل الفئة التي لديها أسلوب إنشاء وحيد وعدة أساليب لتهيئة الكائن الناتج، وتدعم أساليب الباني التسلسل (Chaining) في الغالب. (مثال: باني->ضع قيمةأ(1)->ضع قيمةب(2)->أنشئ() ).

مثال تصوري

يوضح هذا المثال بنية نمط الباني Builder، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.

 index.php: مثال تصوري

<?php

namespace RefactoringGuru\Builder\Conceptual;

/**
 * تحدد واجهة الباني أساليب إنشاء الأجزاء المختلفة لكائنات المنتج.
 */
interface Builder
{
    public function producePartA(): void;

    public function producePartB(): void;

    public function producePartC(): void;
}

/**
 * تتبع فئات الباني الحقيقي واجهة الباني وتوفر تطبيقات محددة لخطوات
 * الإنشاء، وقد يحتوي برنامجك على عدة أشكال للبانيات تُطبق بأشكال مختلفة.
 */
class ConcreteBuilder1 implements Builder
{
    private $product;

    /**
     * يجب أن تحتوي نسخة الباني الحديثة على كائن منتج فارغ يُستخدم
     * في عمليات التجميع التالية.
     */
    public function __construct()
    {
        $this->reset();
    }

    public function reset(): void
    {
        $this->product = new Product1;
    }

    /**
     * تعمل كل خطوات الإنشاء مع نفس النسخة من المنتج.
     */
    public function producePartA(): void
    {
        $this->product->parts[] = "PartA1";
    }

    public function producePartB(): void
    {
        $this->product->parts[] = "PartB1";
    }

    public function producePartC(): void
    {
        $this->product->parts[] = "PartC1";
    }

    /**
     * يفترض بالبانيات الحقيقية أن توفر أساليبها الخاصة
     * بجلب النتائج، لأن الأنواع المختلفة من البانيات قد تنشئ
     * منتجات مختلفة تمامًا لا تتبع نفس الواجهة، لهذا لا يمكن
     * التصريح عن أساليب كتلك في واجهة الباني، على الأقل
     * statically-typed language في لغات البرمجة من النوع الساكن.
     * من النوع الديناميكي، ويمكن إدخال هذا الأسلوب PHP لاحظ أن لغة
     * في الواجهة الأساسية. لكن لن نصرّح عنه هناك لداعي الوضوح والتبسيط.
     *
     * ينبغي أن تكون نسخة الباني جاهزة لبدء إنتاج منتج جديد بعد
     * إعادة النتيجة النهائية إلى العميل. لذا من المعتاد استدعاء أسلوب
     * getProduct إعادة الضبط في نهاية متن أسلوب.
     * لكن هذا السلوك ليس ضروريًا، ويمكنك جعل الباني الخاص بك ينتظر
     * استدعاء إعادة ضبط صريح من شيفرة العميل قبل التخلص من النتيجة
     * السابقة.
     */
    public function getProduct(): Product1
    {
        $result = $this->product;
        $this->reset();

        return $result;
    }
}

/**
 * من المنطقي استخدام نمط الباني فقط حين تكون منتجاتك بها الكثير
 * من التعقيدات وتتطلب تجهيزات كثيرة.
 *
 * على عكس الأنماط الإنشائية الأخرى فإن البانيات الحقيقية المختلفة
 * تنتج منتجات غير مرتبطة ببعضها، أي ليس شرطًا أن تتبع نواتج
 * البانيات المختلفة نفس الواجهة دائمًا.
 */
class Product1
{
    public $parts = [];

    public function listParts(): void
    {
        echo "Product parts: " . implode(', ', $this->parts) . "\n\n";
    }
}

/**
 * لا يكون الموجّه مسؤولًا إلا عن تنفيذ خطوات البناء بتسلسل معين.
 * ذلك مفيد عندما تنتج منتجات وفقًا لترتيب أو إعدادات معينة.
 * وتُعد فئة الباني خيارية بما أن العميل يستطيع التحكم في
 * كائنات البناء مباشرة.
 */
class Director
{
    /**
     * @var Builder
     */
    private $builder;

    /**
     * يعمل الموجّه مع أي نسخة باني تمررها شيفرة العميل إليه، وبهذه الطريقة
     * فإن شيفرة العميل يمكنها تغيير النوع النهائي للمنتج المجمَّع حديثًا.
     */
    public function setBuilder(Builder $builder): void
    {
        $this->builder = $builder;
    }

    /**
     * يستطيع الموجّه إنشاء صور عدة للمنتج باستخدام
     * نفس خطوات البناء.
     */
    public function buildMinimalViableProduct(): void
    {
        $this->builder->producePartA();
    }

    public function buildFullFeaturedProduct(): void
    {
        $this->builder->producePartA();
        $this->builder->producePartB();
        $this->builder->producePartC();
    }
}

/**
 * وتمرره إلى الموجّه builder تنشئ شيفرة العميل كائن الباني
 * ثم تبدأ عملية الإنشاء، وتُجلب النتيجة النهائية.
 * من الكائن الباني.
 */
function clientCode(Director $director)
{
    $builder = new ConcreteBuilder1;
    $director->setBuilder($builder);

    echo "Standard basic product:\n";
    $director->buildMinimalViableProduct();
    $builder->getProduct()->listParts();

    echo "Standard full featured product:\n";
    $director->buildFullFeaturedProduct();
    $builder->getProduct()->listParts();

    // تذكر أن نمط الباني يمكن استخدامه دون فئة الموجّه.
    echo "Custom product:\n";
    $builder->producePartA();
    $builder->producePartC();
    $builder->getProduct()->listParts();
}

$director = new Director;
clientCode($director);

 Output.txt: نتائج التنفيذ

Standard basic product:
Product parts: PartA1

Standard full featured product:
Product parts: PartA1, PartB1, PartC1

Custom product:
Product parts: PartA1, PartC1

مثال واقعي

يُعد باني استعلامات SQL (أو SQL query builder) من أفضل التطبيقات على نمط الباني، وتحدد واجهة الباني الخطوات المشتركة لبناء استعلام SQL عام، وتستخدم البانيات الحقيقية الموافقة للهجات SQL المختلفة، تستخدم تلك الخطوات من خلال إعادة أجزاء من استعلامات SQL التي يمكن تنفيذها في محرك قاعدة بيانات بعينه.

 index.php: مثال واقعي

<?php

namespace RefactoringGuru\Builder\RealWorld;

/**
 * SQL تصرّح واجهة الباني عن مجموعة من الأساليب لتجميع استعلام.
 * 
 * تعيد جميع خطوات الإنشاء كائن البنّاء الحالي لتسمح بالتسلسل التالي:
 * $builder->select(...)->where(...)
 */
interface SQLQueryBuilder
{
    public function select(string $table, array $fields): SQLQueryBuilder;

    public function where(string $field, string $value, string $operator = '='): SQLQueryBuilder;

    public function limit(int $start, int $offset): SQLQueryBuilder;

    // SQL مئاتالأسالسيب الأخرى من استعلامات...

    public function getSQL(): string;
}

/**
 * مختلفة وقد يطبق خطوات الباني بشكل SQL يتوافق كل بنّاء حقيقي مع لهجة
 * مختلف قليلًا عن البقية.
 *
 * MySQL متوافقة مع SQL يستطيع هذا الباني الحقيقي بناء استعلامات
 */
class MysqlQueryBuilder implements SQLQueryBuilder
{
    protected $query;

    protected function reset(): void
    {
        $this->query = new \stdClass;
    }

    /**
     * (base query) أساسي SELECT أنشئ استعلام.
     */
    public function select(string $table, array $fields): SQLQueryBuilder
    {
        $this->reset();
        $this->query->base = "SELECT " . implode(", ", $fields) . " FROM " . $table;
        $this->query->type = 'select';

        return $this;
    }

    /**
     * WHERE أضف شَرطية.
     */
    public function where(string $field, string $value, string $operator = '='): SQLQueryBuilder
    {
        if (!in_array($this->query->type, ['select', 'update'])) {
            throw new \Exception("WHERE can only be added to SELECT OR UPDATE");
        }
        $this->query->where[] = "$field $operator '$value'";

        return $this;
    }

    /**
     * (LIMIT constraint) LIMIT أضف قيد.
     */
    public function limit(int $start, int $offset): SQLQueryBuilder
    {
        if (!in_array($this->query->type, ['select'])) {
            throw new \Exception("LIMIT can only be added to SELECT");
        }
        $this->query->limit = " LIMIT " . $start . ", " . $offset;

        return $this;
    }

    /**
     * اجلب نص الاستعلام الأخير.
     */
    public function getSQL(): string
    {
        $query = $this->query;
        $sql = $query->base;
        if (!empty($query->where)) {
            $sql .= " WHERE " . implode(' AND ', $query->where);
        }
        if (isset($query->limit)) {
            $sql .= $query->limit;
        }
        $sql .= ";";
        return $sql;
    }
}

/**
 * PostgreSQL يتوافق هذا الباني الحقيقي مع
 * إلا أن بها اختلافات عدة MySQL تشبه باني Postgres وبرغم أن.
 * مع تغيير بعض ،MySQL ولإعادة استخدام الشيفرة يجب توسيعها من باني
 * خطوات الإنشاء.
 */
class PostgresQueryBuilder extends MysqlQueryBuilder
{
    /**
     * ضمن أمور أخرى ، LIMIT بنية لغوية مختلفة قليلًا لـ PostgreSQL لدى.
     */
    public function limit(int $start, int $offset): SQLQueryBuilder
    {
        parent::limit($start, $offset);

        $this->query->limit = " LIMIT " . $start . " OFFSET " . $offset;

        return $this;
    }

    // إضافة إلى الكثير من التغييرات...
}


/**
 * لاحظ أن شيفرة العميل تستهدم كائن الباني مباشرة، ولا نحتاج إلى تخصيص 
 * فئة موجه لهذه الحالة لأن شيفرة العميل تحتاج استعلامات مختلفة في كل
 * مرة تقريبًا، لذا لا يمكن إعادة استخدام تسلسل خطوات البناء بسهولة.
 * 
 * (String وبما أن كل بانيات الاستعلامات لدينا تنشئ منتجات من نفس النوع (نص
 * فيمكننا التفاعل مع كل البانيات باستخدام واجهتها المشتركة.
 * سنستطيع لاحقًا تمرير نسخة فئة باني جديدة -إن استخدمناها- إلى شيفرة العميل
 * SQLQueryBuilder الموجودة دون تعطيلها، وذلك بفضل واجهة.
 */
function clientCode(SQLQueryBuilder $queryBuilder)
{
    // ...

    $query = $queryBuilder
        ->select("users", ["name", "email", "password"])
        ->where("age", 18, ">")
        ->where("age", 30, "<")
        ->limit(10, 20)
        ->getSQL();

    echo $query;

    // ...
}


/**
 * يختار التطبيق النوع المناسب لباني الاستعلام بناءً على التهيئة الحالية
 * أو إعدادات البيئة.
 */
// if ($_ENV['database_type'] == 'postgres') {
//     $builder = new PostgresQueryBuilder(); } else {
//     $builder = new MysqlQueryBuilder; }
//
// clientCode($builder);


echo "Testing MySQL query builder:\n";
clientCode(new MysqlQueryBuilder);

echo "\n\n";

echo "Testing PostgresSQL query builder:\n";
clientCode(new PostgresQueryBuilder);

 Output.txt: نتائج التنفيذ

Testing MySQL query builder:
SELECT name, email, password FROM users WHERE age > '18' AND age < '30' LIMIT 10, 20;

Testing PostgresSQL query builder:
SELECT name, email, password FROM users WHERE age > '18' AND age < '30' LIMIT 10 OFFSET 20;

الاستخدام في لغة بايثون

المستوى: ★ ★ ☆

الانتشار: ★ ★ ★

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

يمكن ملاحظة النمط داخل الفئة التي لديها أسلوب إنشاء وحيد وعدة أساليب لتهيئة الكائن الناتج، وتدعم أساليب الباني التسلسل (Chaining) في الغالب. (مثال: باني->ضع قيمةأ(1)->ضع قيمةب(2)->أنشئ() ).

مثال تصوري

يوضح هذا المثال بنية نمط الباني Builder، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 main.py: مثال تصوري

from __future__ import annotations
from abc import ABC, abstractmethod, abstractproperty
from typing import Any


class Builder(ABC):
    """
    تحدد واجهة الباني أساليب إنشاء الأجزاء المختلفة لكائنات المنتج.
    """

    @abstractproperty
    def product(self) -> None:
        pass

    @abstractmethod
    def produce_part_a(self) -> None:
        pass

    @abstractmethod
    def produce_part_b(self) -> None:
        pass

    @abstractmethod
    def produce_part_c(self) -> None:
        pass


class ConcreteBuilder1(Builder):
    """
    تتبع فئات الباني الحقيقي واجهة الباني وتوفر تطبيقات محددة لخطوات
    الإنشاء، وقد يحتوي برنامجك على عدة أشكال للبانيات تُطبق بأشكال مختلفة.
    """

    def __init__(self) -> None:
        """
        يجب أن تحتوي نسخة الباني الحديثة على كائن منتج فارغ يُستخدم
        في عمليات التجميع التالية.
        """
        self.reset()

    def reset(self) -> None:
        self._product = Product1()

    @property
    def product(self) -> Product1:
        """
        يفترض بالبانيات الحقيقية أن توفر أساليبها الخاصة
        بجلب النتائج، لأن الأنواع المختلفة من البانيات قد تنشئ
        منتجات مختلفة تمامًا لا تتبع نفس الواجهة، لهذا لا يمكن
        التصريح عن أساليب كتلك في واجهة الباني، على الأقل
        statically-typed language في لغات البرمجة من النوع الساكن.

        ينبغي أن تكون نسخة الباني جاهزة لبدء إنتاج منتج جديد بعد
        إعادة النتيجة النهائية إلى العميل. لذا من المعتاد استدعاء أسلوب 
        getProduct إعادة الضبط في نهاية متن أسلوب.
        لكن هذا السلوك ليس ضروريًا، ويمكنك جعل الباني الخاص بك ينتظر
        استدعاء إعادة ضبط صريح من شيفرة العميل قبل التخلص من النتيجة
        السابقة.
        """
        product = self._product
        self.reset()
        return product

    def produce_part_a(self) -> None:
        self._product.add("PartA1")

    def produce_part_b(self) -> None:
        self._product.add("PartB1")

    def produce_part_c(self) -> None:
        self._product.add("PartC1")


class Product1():
    """
    من المنطقي استخدام نمط الباني فقط حين تكون منتجاتك بها الكثير
    من التعقيدات وتتطلب تجهيزات كثيرة.

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

    def __init__(self) -> None:
        self.parts = []

    def add(self, part: Any) -> None:
        self.parts.append(part)

    def list_parts(self) -> None:
        print(f"Product parts: {', '.join(self.parts)}", end="")


class Director:
    """
    لا يكون الموجّه مسؤولًا إلا عن تنفيذ خطوات البناء بتسلسل معين.
    ذلك مفيد عندما تنتج منتجات وفقًا لترتيب أو إعدادات معينة.
    وتُعد فئة الباني خيارية بما أن العميل يستطيع التحكم في
    كائنات البناء مباشرة.
    """

    def __init__(self) -> None:
        self._builder = None

    @property
    def builder(self) -> Builder:
        return self._builder

    @builder.setter
    def builder(self, builder: Builder) -> None:
        """
        يعمل الموجّه مع أي نسخة باني تمررها شيفرة العميل إليه، وبهذه الطريقة
        فإن شيفرة العميل يمكنها تغيير النوع النهائي للمنتج المجمَّع حديثًا.
        """
        self._builder = builder

    """
    يستطيع الموجّه إنشاء صور عدة للمنتج باستخدام
    نفس خطوات البناء.
    """

    def build_minimal_viable_product(self) -> None:
        self.builder.produce_part_a()

    def build_full_featured_product(self) -> None:
        self.builder.produce_part_a()
        self.builder.produce_part_b()
        self.builder.produce_part_c()


if __name__ == "__main__":
    """
    وتمررها إلى الموجّه builder تنشئ شيفرة العميل كائن الباني
    ثم تبدأ عملية الإنشاء، وتُجلب النتيجة النهائية.
    من الكائن الباني.
    """

    director = Director()
    builder = ConcreteBuilder1()
    director.builder = builder

    print("Standard basic product: ")
    director.build_minimal_viable_product()
    builder.product.list_parts()

    print("\n")

    print("Standard full featured product: ")
    director.build_full_featured_product()
    builder.product.list_parts()

    print("\n")

    # تذكر أن نمط الباني يمكن استخدامه دون فئة الموجّه.
    print("Custom product: ")
    builder.produce_part_a()
    builder.produce_part_b()
    builder.product.list_parts()

 Output.txt: نتائج التنفيذ

Standard basic product: 
Product parts: PartA1

Standard full featured product: 
Product parts: PartA1, PartB1, PartC1

Custom product: 
Product parts: PartA1, PartB1

الاستخدام في لغة روبي

المستوى: ★ ★ ☆

الانتشار: ★ ★ ★

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

يمكن ملاحظة النمط داخل الفئة التي لديها أسلوب إنشاء وحيد وعدة أساليب لتهيئة الكائن الناتج، وتدعم أساليب الباني التسلسل (Chaining) في الغالب. (مثال: باني->ضع قيمةأ(1)->ضع قيمةب(2)->أنشئ() ).

مثال تصوري

يوضح هذا المثال بنية نمط الباني Builder، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 main.rb: مثال تصوري

# تحدد واجهة الباني أساليب إنشاء الأجزاء المختلفة لكائنات المنتج.
class Builder
  # @abstract
  def produce_part_a
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end

  # @abstract
  def produce_part_b
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end

  # @abstract
  def produce_part_c
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

# تتبع فئات الباني الحقيقي واجهة الباني وتوفر تطبيقات محددة لخطوات
# الإنشاء، وقد يحتوي برنامجك على عدة أشكال للبانيات تُطبق بأشكال مختلفة.
class ConcreteBuilder1 < Builder
  # يجب أن تحتوي نسخة الباني الحديثة على كائن منتج فارغ يُستخدم
  # في عمليات التجميع التالية.
  def initialize
    reset
  end

  def reset
    @product = Product1.new
  end

  # يفترض بالبانيات الحقيقية أن توفر أساليبها الخاصة
  # بجلب النتائج، لأن الأنواع المختلفة من البانيات قد تنشئ
  # منتجات مختلفة تمامًا لا تتبع نفس الواجهة، لهذا لا يمكن
  # التصريح عن أساليب كتلك في واجهة الباني، على الأقل
  # statically-typed language في لغات البرمجة من النوع الساكن.
  # 
  # ينبغي أن تكون نسخة الباني جاهزة لبدء إنتاج منتج جديد بعد
  # إعادة النتيجة النهائية إلى العميل. لذا من المعتاد استدعاء أسلوب 
  # getProduct إعادة الضبط في نهاية متن أسلوب.
  # لكن هذا السلوك ليس ضروريًا، ويمكنك جعل الباني الخاص بك ينتظر
  # استدعاء إعادة ضبط صريح من شيفرة العميل قبل التخلص من النتيجة
  # السابقة.
  def product
    product = @product
    reset
    product
  end

  def produce_part_a
    @product.add('PartA1')
  end

  def produce_part_b
    @product.add('PartB1')
  end

  def produce_part_c
    @product.add('PartC1')
  end
end

# من المنطقي استخدام نمط الباني فقط حين تكون منتجاتك بها الكثير
# من التعقيدات وتتطلب تجهيزات كثيرة.

# على عكس الأنماط الإنشائية الأخرى فإن البانيات الحقيقية المختلفة
# تنتج منتجات غير مرتبطة ببعضها، أي ليس شرطًا أن تتبع نواتج
# البانيات المختلفة نفس الواجهة دائمًا.
class Product1
  def initialize
    @parts = []
  end

  # @param [String] part
  def add(part)
    @parts << part
  end

  def list_parts
    print "Product parts: #{@parts.join(', ')}"
  end
end

# لا يكون الموجّه مسؤولًا إلا عن تنفيذ خطوات البناء بتسلسل معين.
# ذلك مفيد عندما تنتج منتجات وفقًا لترتيب أو إعدادات معينة.
# وتُعد فئة الباني خيارية بما أن العميل يستطيع التحكم في
# كائنات البناء مباشرة.
class Director
  # @return [Builder]
  attr_accessor :builder

  def initialize
    @builder = nil
  end

  # يعمل الموجّه مع أي نسخة باني تمررها شيفرة العميل إليه، وبهذه الطريقة
  # فإن شيفرة العميل يمكنها تغيير النوع النهائي للمنتج المجمَّع حديثًا.
  def builder=(builder)
    @builder = builder
  end

  # يستطيع الموجّه إنشاء صور عدة للمنتج باستخدام
  # نفس خطوات البناء.

  def build_minimal_viable_product
    @builder.produce_part_a
  end

  def build_full_featured_product
    @builder.produce_part_a
    @builder.produce_part_b
    @builder.produce_part_c
  end
end

# وتمررها إلى الموجّه builder تنشئ شيفرة العميل كائن الباني
# ثم تبدأ عملية الإنشاء، وتُجلب النتيجة النهائية.
# من الكائن الباني.

director = Director.new
builder = ConcreteBuilder1.new
director.builder = builder

puts 'Standard basic product: '
director.build_minimal_viable_product
builder.product.list_parts

puts "\n\n"

puts 'Standard full featured product: '
director.build_full_featured_product
builder.product.list_parts

puts "\n\n"

# تذكر أن نمط الباني يمكن استخدامه دون فئة الموجّه.
puts 'Custom product: '
builder.produce_part_a
builder.produce_part_b
builder.product.list_parts

 output.txt: نتائج التنفيذ

Standard basic product: 
Product parts: PartA1

Standard full featured product: 
Product parts: PartA1, PartB1, PartC1

Custom product: 
Product parts: PartA1, PartB1

الاستخدام في لغة Swift

المستوى: ★ ★ ☆

الانتشار: ★ ★ ★

أمثلة الاستخدام: ينتشر استخدام نمط الباني في لغة Swift، إذ هو مفيد جدًا حين تريد إنشاء كائن به الكثير من خيارات التهيئة المحتملة.

يمكن ملاحظة النمط داخل الفئة التي لديها أسلوب إنشاء وحيد وعدة أساليب لتهيئة الكائن الناتج، وتدعم أساليب الباني التسلسل (Chaining) في الغالب. (مثال: باني->ضع قيمةأ(1)->ضع قيمةب(2)->أنشئ() ).

مثال تصوري

يوضح هذا المثال بنية نمط الباني Builder، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift.

 Example.swift: مثال تصوري

import XCTest

/// تحدد واجهة الباني أساليب إنشاء الأجزاء المختلفة لكائنات المنتج.
protocol Builder {

    func producePartA()
    func producePartB()
    func producePartC()
}

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

class ConcreteBuilder1: Builder {

    /// يجب أن تحتوي نسخة الباني الحديثة على كائن منتج فارغ يُستخدم
    /// في عمليات التجميع التالية.

    private var product = Product1()

    func reset() {
        product = Product1()
    }

    /// تعمل كل خطوات الإنشاء مع نفس النسخة من المنتج.
    func producePartA() {
        product.add(part: "PartA1")
    }

    func producePartB() {
        product.add(part: "PartB1")
    }

    func producePartC() {
        product.add(part: "PartC1")
    }

    /// يفترض بالبانيات الحقيقية أن توفر أساليبها الخاصة
    /// بجلب النتائج، لأن الأنواع المختلفة من البانيات قد تنشئ
    /// منتجات مختلفة تمامًا لا تتبع نفس الواجهة، لهذا لا يمكن
    /// التصريح عن أساليب كتلك في واجهة الباني، على الأقل
    /// statically-typed language في لغات البرمجة من النوع الساكن.
    /// 
    /// ينبغي أن تكون نسخة الباني جاهزة لبدء إنتاج منتج جديد بعد
    /// إعادة النتيجة النهائية إلى العميل. لذا من المعتاد استدعاء أسلوب 
    /// getProduct إعادة الضبط في نهاية متن أسلوب.
    /// لكن هذا السلوك ليس ضروريًا، ويمكنك جعل الباني الخاص بك ينتظر
    /// استدعاء إعادة ضبط صريح من شيفرة العميل قبل التخلص من النتيجة
    /// السابقة.
    func retrieveProduct() -> Product1 {
        let result = self.product
        reset()
        return result
    }
}

/// لا يكون الموجّه مسؤولًا إلا عن تنفيذ خطوات البناء بتسلسل معين.
/// ذلك مفيد عندما تنتج منتجات وفقًا لترتيب أو إعدادات معينة.
/// وتُعد فئة الباني خيارية بما أن العميل يستطيع التحكم في
/// كائنات البناء مباشرة.
class Director {

    private var builder: Builder?

    /// يعمل الموجّه مع أي نسخة باني تمررها شيفرة العميل إليه، وبهذه الطريقة
    /// فإن شيفرة العميل يمكنها تغيير النوع النهائي للمنتج المجمَّع حديثًا.
    func update(builder: Builder) {
        self.builder = builder
    }

    /// يستطيع الموجّه إنشاء صور عدة للمنتج باستخدام
    /// نفس خطوات البناء.
    func buildMinimalViableProduct() {
        builder?.producePartA()
    }

    func buildFullFeaturedProduct() {
        builder?.producePartA()
        builder?.producePartB()
        builder?.producePartC()
    }
}

/// من المنطقي استخدام نمط الباني فقط حين تكون منتجاتك بها الكثير
/// من التعقيدات وتتطلب تجهيزات كثيرة.

/// على عكس الأنماط الإنشائية الأخرى فإن البانيات الحقيقية المختلفة
/// تنتج منتجات غير مرتبطة ببعضها، أي ليس شرطًا أن تتبع نواتج
/// البانيات المختلفة نفس الواجهة دائمًا.
class Product1 {

    private var parts = [String]()

    func add(part: String) {
        self.parts.append(part)
    }

    func listParts() -> String {
        return "Product parts: " + parts.joined(separator: ", ") + "\n"
    }
}

/// وتمررها إلى الموجّه builder تنشئ شيفرة العميل كائن الباني
/// ثم تبدأ عملية الإنشاء، وتُجلب النتيجة النهائية.
/// من الكائن الباني.
class Client {
    // ...
    static func someClientCode(director: Director) {
        let builder = ConcreteBuilder1()
        director.update(builder: builder)
        
        print("Standard basic product:")
        director.buildMinimalViableProduct()
        print(builder.retrieveProduct().listParts())

        print("Standard full featured product:")
        director.buildFullFeaturedProduct()
        print(builder.retrieveProduct().listParts())

        // تذكر أن نمط الباني يمكن استخدامه دون فئة الموجّه.
        print("Custom product:")
        builder.producePartA()
        builder.producePartC()
        print(builder.retrieveProduct().listParts())
    }
    // ...
}

/// لنرى الآن كيف ستعمل تلك الشيفرة..
class BuilderConceptual: XCTestCase {

    func testBuilderConceptual() {
        var director = Director();
        Client.someClientCode(director: director)
    }
}

 Output.txt: نتائج التنفيذ

Standard basic product:
Product parts: PartA1

Standard full featured product:
Product parts: PartA1, PartB1, PartC1

Custom product:
Product parts: PartA1, PartC1

مثال واقعي

 Example.swift: شيفرة العميل

import Foundation
import XCTest


class BaseQueryBuilder<Model: DomainModel> {

    typealias Predicate = (Model) -> (Bool)

    func limit(_ limit: Int) -> BaseQueryBuilder<Model> {
        return self
    }

    func filter(_ predicate: @escaping Predicate) -> BaseQueryBuilder<Model> {
        return self
    }

    func fetch() -> [Model] {
        preconditionFailure("Should be overridden in subclasses.")
    }
}

class RealmQueryBuilder<Model: DomainModel>: BaseQueryBuilder<Model> {

    enum Query {
        case filter(Predicate)
        case limit(Int)
        /// ...
    }

    fileprivate var operations = [Query]()

    @discardableResult
    override func limit(_ limit: Int) -> RealmQueryBuilder<Model> {
        operations.append(Query.limit(limit))
        return self
    }

    @discardableResult
    override func filter(_ predicate: @escaping Predicate) -> RealmQueryBuilder<Model> {
        operations.append(Query.filter(predicate))
        return self
    }

    override func fetch() -> [Model] {
        print("RealmQueryBuilder: Initializing CoreDataProvider with \(operations.count) operations:")
        return RealmProvider().fetch(operations)
    }
}

class CoreDataQueryBuilder<Model: DomainModel>: BaseQueryBuilder<Model> {

    enum Query {
        case filter(Predicate)
        case limit(Int)
        case includesPropertyValues(Bool)
        /// ...
    }

    fileprivate var operations = [Query]()

    override func limit(_ limit: Int) -> CoreDataQueryBuilder<Model> {
        operations.append(Query.limit(limit))
        return self
    }

    override func filter(_ predicate: @escaping Predicate) -> CoreDataQueryBuilder<Model> {
        operations.append(Query.filter(predicate))
        return self
    }

    func includesPropertyValues(_ toggle: Bool) -> CoreDataQueryBuilder<Model> {
        operations.append(Query.includesPropertyValues(toggle))
        return self
    }

    override func fetch() -> [Model] {
        print("CoreDataQueryBuilder: Initializing CoreDataProvider with \(operations.count) operations.")
        return CoreDataProvider().fetch(operations)
    }
}


/// (Builders) ُوتجمِّع البانيات (Data Providers) تحتوي مزودات البيانات 
/// العملياتَ ثم تحدّث المزودات لجلب البيانات.

class RealmProvider {

    func fetch<Model: DomainModel>(_ operations: [RealmQueryBuilder<Model>.Query]) -> [Model] {

        print("RealmProvider: Retrieving data from Realm...")

        for item in operations {
            switch item {
            case .filter(_):
                print("RealmProvider: executing the 'filter' operation.")
                /// لفلترة النتائج Realm استخدم نسخة.
                break
            case .limit(_):
                print("RealmProvider: executing the 'limit' operation.")
                /// لتقييد النتائج Realm استخدم نسخة.
                break
            }
        }

        /// Realm أعد النتائج من
        return []
    }
}

class CoreDataProvider {

    func fetch<Model: DomainModel>(_ operations: [CoreDataQueryBuilder<Model>.Query]) -> [Model] {

        /// NSFetchRequest أنشئ.

        print("CoreDataProvider: Retrieving data from CoreData...")

        for item in operations {
            switch item {
            case .filter(_):
                print("CoreDataProvider: executing the 'filter' operation.")
                /// NSFetchRequest لـ predicate حدد.
                break
            case .limit(_):
                print("CoreDataProvider: executing the 'limit' operation.")
                /// NSFetchRequest لـ fetchLimit حدد.
                break
            case .includesPropertyValues(_):
                print("CoreDataProvider: executing the 'includesPropertyValues' operation.")
                /// NSFetchRequest لـ includesPropertyValues حدد.
                break
            }
        }

        /// وأعد النتائج NSFetchRequest نفذ.
        return []
    }
}


protocol DomainModel {
    /// يجمع البروتوكول موديلات النطاق إلى الواجهة المشتركة.
}

private struct User: DomainModel {
    let id: Int
    let age: Int
    let email: String
}


class BuilderRealWorld: XCTestCase {

    func testBuilderRealWorld() {
        print("Client: Start fetching data from Realm")
        clientCode(builder: RealmQueryBuilder<User>())

        print()

        print("Client: Start fetching data from CoreData")
        clientCode(builder: CoreDataQueryBuilder<User>())
    }

    fileprivate func clientCode(builder: BaseQueryBuilder<User>) {

        let results = builder.filter({ $0.age < 20 })
            .limit(1)
            .fetch()

        print("Client: I have fetched: " + String(results.count) + " records.")
    }
}

 Output.txt: نتائح التنفيذ

Client: Start fetching data from Realm
RealmQueryBuilder: Initializing CoreDataProvider with 2 operations:
RealmProvider: Retrieving data from Realm...
RealmProvider: executing the 'filter' operation.
RealmProvider: executing the 'limit' operation.
Client: I have fetched: 0 records.

Client: Start fetching data from CoreData
CoreDataQueryBuilder: Initializing CoreDataProvider with 2 operations.
CoreDataProvider: Retrieving data from CoreData...
CoreDataProvider: executing the 'filter' operation.
CoreDataProvider: executing the 'limit' operation.
Client: I have fetched: 0 records.

الاستخدام في لغة TypeScript

المستوى: ★ ★ ☆

الانتشار: ★ ★ ★

أمثلة الاستخدام: ينتشر استخدام نمط الباني في لغة TypeScript، إذ هو مفيد جدًا حين تريد إنشاء كائن به الكثير من خيارات التهيئة المحتملة.

يمكن ملاحظة النمط داخل الفئة التي لديها أسلوب إنشاء وحيد وعدة أساليب لتهيئة الكائن الناتج، وتدعم أساليب الباني التسلسل (Chaining) في الغالب. (مثال: باني->ضع قيمةأ(1)->ضع قيمةب(2)->أنشئ() ).

مثال تصوري

يوضح هذا المثال بنية نمط الباني Builder، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 index.ts: مثال تصوري

/**
 * تحدد واجهة الباني أساليب إنشاء الأجزاء المختلفة لكائنات المنتج.
 */
interface Builder {
    producePartA(): void;
    producePartB(): void;
    producePartC(): void;
}

/**
 * تتبع فئات الباني الحقيقي واجهة الباني وتوفر تطبيقات محددة لخطوات
 * الإنشاء، وقد يحتوي برنامجك على عدة أشكال للبانيات تُطبق بأشكال مختلفة.
 */
class ConcreteBuilder1 implements Builder {
    private product: Product1;

    /**
     * يجب أن تحتوي نسخة الباني الحديثة على كائن منتج فارغ يُستخدم
     * في عمليات التجميع التالية.
     */
    constructor() {
        this.reset();
    }

    public reset(): void {
        this.product = new Product1();
    }

    /**
     * تعمل كل خطوات الإنشاء مع نفس النسخة من المنتج.
     */
    public producePartA(): void {
        this.product.parts.push('PartA1');
    }

    public producePartB(): void {
        this.product.parts.push('PartB1');
    }

    public producePartC(): void {
        this.product.parts.push('PartC1');
    }

    /**
     * يفترض بالبانيات الحقيقية أن توفر أساليبها الخاصة
     * بجلب النتائج، لأن الأنواع المختلفة من البانيات قد تنشئ
     * منتجات مختلفة تمامًا لا تتبع نفس الواجهة، لهذا لا يمكن
     * التصريح عن أساليب كتلك في واجهة الباني، على الأقل
     * statically-typed language في لغات البرمجة من النوع الساكن.
     * 
     * ينبغي أن تكون نسخة الباني جاهزة لبدء إنتاج منتج جديد بعد
     * إعادة النتيجة النهائية إلى العميل. لذا من المعتاد استدعاء أسلوب 
     * getProduct إعادة الضبط في نهاية متن أسلوب.
     * لكن هذا السلوك ليس ضروريًا، ويمكنك جعل الباني الخاص بك ينتظر
     * استدعاء إعادة ضبط صريح من شيفرة العميل قبل التخلص من النتيجة
     * السابقة.
     */
    public getProduct(): Product1 {
        const result = this.product;
        this.reset();
        return result;
    }
}

/**
 * من المنطقي استخدام نمط الباني فقط حين تكون منتجاتك بها الكثير
 * من التعقيدات وتتطلب تجهيزات كثيرة.
 *
 * على عكس الأنماط الإنشائية الأخرى فإن البانيات الحقيقية المختلفة
 * تنتج منتجات غير مرتبطة ببعضها، أي ليس شرطًا أن تتبع نواتج
 * البانيات المختلفة نفس الواجهة دائمًا.
 */
class Product1 {
    public parts: string[] = [];

    public listParts(): void {
        console.log(`Product parts: ${this.parts.join(', ')}\n`);
    }
}

/**
 * لا يكون الموجّه مسؤولًا إلا عن تنفيذ خطوات البناء بتسلسل معين.
 * ذلك مفيد عندما تنتج منتجات وفقًا لترتيب أو إعدادات معينة.
 * وتُعد فئة الباني خيارية بما أن العميل يستطيع التحكم في
 * كائنات البناء مباشرة.
 */
class Director {
    private builder: Builder;

    /**
     * يعمل الموجّه مع أي نسخة باني تمررها شيفرة العميل إليه، وبهذه الطريقة
     * فإن شيفرة العميل يمكنها تغيير النوع النهائي للمنتج المجمَّع حديثًا.
     */
    public setBuilder(builder: Builder): void {
        this.builder = builder;
    }

    /**
     * يستطيع الموجّه إنشاء صور عدة للمنتج باستخدام
     * نفس خطوات البناء.
     */
    public buildMinimalViableProduct(): void {
        this.builder.producePartA();
    }

    public buildFullFeaturedProduct(): void {
        this.builder.producePartA();
        this.builder.producePartB();
        this.builder.producePartC();
    }
}

/**
 * وتمررها إلى الموجّه builder تنشئ شيفرة العميل كائن الباني
 * ثم تبدأ عملية الإنشاء، وتُجلب النتيجة النهائية.
 * من الكائن الباني.
 */
function clientCode(director: Director) {
    const builder = new ConcreteBuilder1();
    director.setBuilder(builder);

    console.log('Standard basic product:');
    director.buildMinimalViableProduct();
    builder.getProduct().listParts();

    console.log('Standard full featured product:');
    director.buildFullFeaturedProduct();
    builder.getProduct().listParts();

    // تذكر أن نمط الباني يمكن استخدامه دون فئة الموجّه.
    console.log('Custom product:');
    builder.producePartA();
    builder.producePartC();
    builder.getProduct().listParts();
}

const director = new Director();
clientCode(director);

 Output.txt: نتائج التنفيذ

Standard basic product:
Product parts: PartA1

Standard full featured product:
Product parts: PartA1, PartB1, PartC1

Custom product:
Product parts: PartA1, PartC1

انظر أيضًا

مصادر