الفرق بين المراجعتين ل"Design Patterns/builder"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(1.0: عنوان الصفحة)
 
(2.0 محتوى)
سطر 1: سطر 1:
 
<noinclude>{{DISPLAYTITLE:نمط الباني Builder}}</noinclude>
 
<noinclude>{{DISPLAYTITLE:نمط الباني Builder}}</noinclude>
 +
نمط الباني (Builder) هو نمط تصميم إنشائي يسمح لك ببناء كائنات معقدة خطوة خطوة، كما يمكنك من إنتاج أنواعًا وتمثيلات (representations) مختلفة من الكائن باستخدام نفس شيفرة البناء (Construction code).
 +
 +
== المشكلة ==
 +
إن كان لدينا كائن معقد يتطلب تهيئة مرهقة وتفصيلية للكثير من الحقول والكائنات المتداخلة (nested objects)، فإن شيفرة التهيئة اللازمة لذلك الكائن تُدفن عادة داخل منشئ هائل الحجم به معامِلات (parameters) كثيرة، أو أسوأ من ذلك، أن تتناثر تلك الشيفرة في أرجاء شيفرة العميل.
 +
 +
ضع الصورة.
 +
 +
(ش.1) قد تجعل البرنامج معقدًا للغاية إن أنشأت فئة فرعية لكل تهيئة محتملة للكائن.
 +
 +
فمثلًا، دعنا ننظر في كيفية إنشاء كائن منزل <code>House</code>: نحتاج أولًا إلى بناء أربع جدران وأرضية، وتركيب الباب، ونافذتين، وكذلك بناء السقف. لكن إن أردت منزلًا أكبر مع فناء خلفي وأنظمة تدفئة وسباكة وكهرباء وغير ذلك، فإن الحل الأبسط لذلك هو توسيع فئة House الأساسية وإنشاء مجموعة من الفئات الفرعية لتغطية جميع تجميعات (combinations) المعامِلات، لكن سيكون لديك في النهاية عدد كبير من الفئات الفرعية، وستزيد الشيفرة والهرمية عند إضافة أي معامِل جديد،  مثل إضافة رواق للمنزل.
 +
 +
هناك منظور آخر لا يحتاج إضافة الفئات الفرعية، وهو إنشاء منشئ ضخم داخل فئة House مع كل المعامِلات المحتملة التي تتحكم في كائن المنزل، وسيلغي هذا الحلُ الحاجةَ إلى الفئات الفرعية، لكنه سيحدث مشكلة أخرى.
 +
 +
الصورة.
 +
 +
المنشئ الذي فيه معامِلات كثيرة مشكلته أن كل المعامِلات مطلوبة دائمًا.
 +
 +
ستكون أكثر المعامِلات غير مستخدمة في أغلب الوقت، مما يجعل المنشئ كبيرًا بدون داعي، فمثلًا قد تحتاج نسبة قليلة من المنازل حمامًا للسباحة، لذا ستكون المعامِلات المطلوبة لحمام السباحة غير مستخدمة في 90% من الوقت مثلًا.
 +
 +
== الحل ==
 +
يقترح نمط الباني (Builder) أن تستخرج شيفرة بناء الكائن من فئته وتنقلها إلى كائنات أخرى تسمى builders.
 +
 +
الصورة.
 +
 +
يسمح لك نمط الباني ببناء كائنات معقدة خطوة بخطوة، ولا يسمح في نفس الوقت للكائنات الأخرى بالوصول إلى المنتج أثناء بنائه.
 +
 +
ينظم النمط بناء الكائن في مجموعة خطوات (buildWalls, buildDoor، إلخ)، وستنفذ سلسلة من تلك الخطوات على كائن باني من أجل إنشاء كائن ما، والمهم هنا أنك لا تحتاج إلى استدعاء جميع الخطوات، بل تستدعي تلك المطلوبة لإنتاج تجهيزات معينة للكائن.
 +
 +
قد تحتاج بعض خطوات البناء إلى تطبيقات مختلفة عندما تريد بناء أشكال مختلفة من المنتج، فمثلًا قد يُنشأ حائط غرفة لكابينة صغيرة من الخشب، لكن جدران القلعة يجب أن تكون من الحجارة. لذا ربما تود إنشاء عدة فئات مختلفة من فئات الباني التي تستخدم نفس مجموعة خطوات البناء، لكن بشكل مختلف، ثم تستطيع استخدام تلك البانيات في عملية البناء -كاستدعاءات مرتبة إلى خطوات البناء مثلًا- من أجل إنتاج أنواع مختلفة من الكائنات.
 +
 +
الصورة.
 +
 +
تنفذ كائنات البناء المختلفة نفس المهام بطرق مختلفة.
 +
 +
فمثلًا، تخيل بانيًا يبني كل شيء من الخشب والزجاج، وآخر يبني بالحجارة والحديد، وآخر يستخدم الذهب والماس، فإن استدعينا نفس الخطوات مع كل باني سنحصل على منزل عادي من الأول، وقلعة صغيرة من الثاني، وقصر من الباني الثالث. لكن ذلك الأسلوب يصلح فقط إن كانت شيفرة العميل التي تستدعي خطوات البناء قادرة على التفاعل مع البنائين باستخدام واجهة مشتركة.
 +
 +
=== الموجِّه Director ===
 +
يمكنك استخراج سلسلة استدعاءات إلى خطوات الباني الذي تستخدمه من أجل إنشاء المنتج إلى فئة منفصلة تسمى الموجّه، وتحدد فئة الموجه تلك ترتيب تنفيذ خطوات البناء، بينما يوفر الباني التطبيق المناسب لتلك الخطوات.
 +
 +
الصورة.
 +
 +
يحدد الموجه خطوات البناء التي يجب تنفيذها للحصول على منتج يعمل بكفاءة.
 +
 +
ليس من الضروري وجود موجّه في برنامجك، بل تستطيع أن تستدعي خطوات البناء بالترتيب الذي تريد مباشرة من شيفرة العميل، لكن فئة الموجه ستكون مكانًا جيدًا لوضع روتين بناء يمكن إعادة استخدامه في بقية برنامجك. أيضًا فإن فئة الموجه تخفي تفاصيل إنشاء المنتج تمامًا من شيفرة العميل، فلا يحتاج العميل إلا إلى ربط الباني بالموجه وبدء البناء باستخدام الموجه، ثم الحصول على النتيجة من الباني.
 +
 +
== البنية ==
 +
# تصرح واجهة الباني (Builder) عن خطوات إنشاء المنتج المشتركة بين كل أنواع كائنات البناء.
 +
# توفر كائنات البناء الحقيقية (Concrete Builders) استخدامات مختلفة لخطوات الإنشاء، وقد تنتج منتجات لا تتبع الواجهة المشتركة.
 +
# المنتجات (Products) هي كائنات ناتجة تُبنى بواسطة كائنات بناء مختلفة لا تنتمي بالضرورة إلى نفس الهرمية الفئوية أو إلى نفس الواجهة.
 +
# تحدد فئة الموجه Director الترتيب الذي يجب أن تُستدعى به خطوات الإنشاء كي تستطيع إنشاء وإعادة استخدام إعدادات بعينها من المنتجات.
 +
# يجب أن يربط العميل Client أحد كائنات البناء مع الموجّه، عادة يتم الأمر مرة واحدة فقط من خلال معامِلات منشئ الموجّه (Director's Constructor)، ثم يستخدم الموجه كائن البناء ذاك في كل الإنشاءات التالية، لكن هناك طريقة أخرى حين يمرِّر العميل كائن البناء إلى أسلوب الإنتاج الخاص بالموجّه، فعندئذ تستطيع استخدام كائن بناء مختلف في كل مرة تنتج فيها شيئًا بواسطة الموجّه.
 +
 +
== مثال توضيحي ==
 +
يوضح هذا المثال لنمط الباني كيفية إعادة استخدام نفس شيفرة بناء الكائن عند بناء أنواع مختلفة من المنتجات كالسيارات مثلًا، وكذلك إنشاء الأدلة الخاصة بها.
 +
 +
الصورة.
 +
 +
مثال تفصيلي لبناء سيارات وأدلة استخدام تناسب موديلات تلك السيارات.
 +
 +
السيارة كائن معقد يمكن إنشاؤه بمئات الطرق المختلفة، وبدلًا من حشو فئة Car بمنشئ ضخم فإننا استخرجنا شيفرة تجميع السيارة إلى فئة منفصلة لبناء السيارة تحتوي على مجموعة من الأساليب الخاصة بتهيئة أجزاء السيارة المختلفة. فإن احتاجت شيفرة العميل أن تجمع طرازًا خاصًا من السيارة فإنها تعمل مع الباني مباشرة، لكن من الناحية الأخرى فإنها تستطيع أن تفوض عملية التجميع إلى فئة الموجه التي تعرف كيف تستخدم الباني لإنشاء الطرازات المشهورة من السيارات.
 +
 +
ولأن السيارة تحتاج إلى دليل استخدام كل خاصية في السيارة، فإن أدلة الطرز المختلفة من السيارات تختلف كذلك، لهذا من المنطقي إعادة استخدام عملية بناء موجودة مسبقًا من أجل السيارات وأدلة استخدامها. وقد لا يكون بناء الدليل مشابهًا لبناء السيارة، لهذا يجب أن نوفر فئة بنّاء أخرى تتخصص في إنشاء الأدلة، وتستخدم تلك الفئة نفس أساليب البناء كأختها التي تبني السيارات، لكن بدلًا من بناء أجزاء السيارة فإنها تصف تلك الأجزاء وتشرحها، ونستطيع بناء أي منهما -الدليل أو السيارة- بتمرير هذه الكائنات البانية إلى نفس كائن الموجه.
 +
 +
والجزء الأخير هو جلب الكائن الناتج، وهو الدليل الورقي والسيارة المعدنية اللذان يختلفان تمامًا عن بعضهما رغم ارتباطهما بشكل ما. ولا تستطيع وضع أسلوب لجلب النتائج في الموجه دون ربطه بفئات المنتج الحقيقية، لهذا نحصل على نتيجة الإنشاء من الباني الذي نفذ المهمة.<syntaxhighlight lang="java">
 +
// يكون استخدام نمط الباني منطقيًا حين تكون منتجاتك
 +
// معقدة وتتطلب إعدادات كثيرة. والمنتجان التاليان
 +
// يرتبطان ببعضهما رغم أنهما ليس لهما واجهة مشتركة.
 +
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()
 +
</syntaxhighlight>
 +
 +
== قابلية التطبيق ==
 +
* '''استخدم نمط الباني للتخلص من المنشئ التليسكوبي (Telescopic Constructor).'''
 +
لنقل أن لديك منشئًا فيه عشر معامِلات اختيارية، ستجد أن استدعاء منشئًا بهذا الحجم غير عملي، لهذا ستحمل المنشئ فوق طاقته وتنشئ عدة إصدارات أخرى أقصر بمعامِلات أقل. ستشير تلك المنشئات إلى المنشئ الأساسي وتمرر بعض القيم الافتراضية إلى أي معامِلات مهملة.<syntaxhighlight lang="java">
 +
class Pizza {
 +
    Pizza(int size) { ... }
 +
    Pizza(int size, boolean cheese) { ... }
 +
    Pizza(int size, boolean cheese, boolean pepperoni) { ... }
 +
    // ...
 +
</syntaxhighlight>يسمح لك نمط الباني ببناء كائنات خطوة بخطوة باستخدام الخطوات التي تحتاجها فقط، ولا تحتاج إلى حشر عشرات المعامِلات داخل منشئاتك بعد تطبيق النمط.
 +
* '''استخدم نمط الباني حين تريد لشيفرتك أن تكون قادرة على إنشاء أشكال مختلفة من منتج ما (كبناء بيت من الخشب وآخر من الحجارة مثلًا).'''
 +
يمكن استخدام النمط حين يتطلب إنشاء أشكال مختلفة من المنتج خطوات متشابهة ولا تختلف إلا في التفاصيل. ذلك أن الباني الأساسي (Base Builder) يحدد جميع خطوات الإنشاء المحتملة، وتستخدم البانيات الحقيقية تلك الخطوات لإنشاء أشكال مختلفة من المنتج بينما ترشد فئة الموجّه ترتيب الإنشاء.
 +
* '''استخدم  الباني لإنشاء أشجار نمط المركَّب أو أي كائنات معقدة أخرى.'''
 +
يسمح لك نمط المركب بإنشاء منتجات خطوة بخطوة، فتستطيع تأجيل تنفيذ بعض الخطوات دون تعطيل المنتج النهائي، بل تستطيع استدعاء الخطوات بشكل عكسي، وذلك مفيد حين تريد بناء شجرة كائنات. أيضًا، لا يكشف الباني المنتج غير المكتمل أثناء تنفيذ خطوات الإنشاء، كي يمنع شيفرة العميل من جلب نتيجة غير مكتملة.
 +
 +
== كيفية الاستخدام ==
 +
* تأكد أنك تحدد بوضوح خطوات الإنشاء لكل أشكال المنتج المتاحة، وإلا فلن تستطيع المضي قدمًا في تطبيق النمط.
 +
* صرح عن هذه الخطوات في واجهة الباني المشتركة.
 +
* أنشئ فئة بنّاء حقيقية لكل شكل من أشكال المنتج وطبق خطوات الإنشاء الخاصة به. لا تنسى تطبيق أسلوب لجلب نتيجة البناء، لا يمكن التصريح عن هذا الأسلوب داخل واجهة الباني بسبب أن البانيات المختلفة قد تبني منتجات ليس لها واجهة مشتركة، لهذا فإنك لا تعرف نوع الإعادة لمثل ذلك الأسلوب، لكن بأي حال، فإن كنت تتعامل مع منتجات من هرمية واحدة، فيمكن إضافة أسلوب الجلب Fetching Method إلى الواجهة المشتركة.
 +
* فكر في إنشاء فئة موجّهDirector Class، ستغلف تلك الفئة طرقًا مختلفة لبناء منتج باستخدام نفس كائن الباني.
 +
* تنشئ شيفرة العميل كائنات الباني والموجّه، ويجب أن يمرِّر العميل كائنًا بانيًا إلى الموجِّه قبل بدء الإنشاء، وعادة ما يفعل العميل ذلك مرة واحدة فقط من خلال معامِلات منشئ الموجّه (Parmeters of the Director's Constructor)، ويستخدم الموجه كائن البنّاء في كل عمليات الإنشاء التالية، يوجد منظور مختلف لهذا يُمرَّر فيه البنّاء مباشرة إلى أسلوب الإنشاء للموجّه.
 +
* يمكن الحصول على نتيجة الإنشاء مباشرة من الموجّه إن كانت كل المنتجات تتبع نفس الواجهة، وإلا فإن العميل يجب أن يجلب النتيجة من البنّاء.
 +
 +
== المزايا والعيوب ==
 +
 +
=== المزايا ===
 +
* تستطيع إنشاء الكائنات خطوة بخطوة، وتؤجل خطوات الإنشاء أو تنفذ الخطوات بشكل معكوس.
 +
* تستطيع إعادة استخدام نفس شيفرة الإنشاء أثناء بناء أشكال مختلفة من المنتجات.
 +
* مبدأ المسؤولية الواحدة. تستطيع عزل شيفرة البناء المعقدة عن منطق العمل للمنتج.
 +
 +
=== العيوب ===
 +
* يزيد التعقيد الكلي للشيفرة بما أن النمط يتطلب إنشاء عدة فئات جديدة.
 +
 +
== العلاقات مع الأنماط الأخرى ==

مراجعة 22:56، 24 سبتمبر 2019

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

المشكلة

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

ضع الصورة.

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

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

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

الصورة.

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

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

الحل

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

الصورة.

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

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

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

الصورة.

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

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

الموجِّه Director

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

الصورة.

يحدد الموجه خطوات البناء التي يجب تنفيذها للحصول على منتج يعمل بكفاءة.

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

البنية

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

مثال توضيحي

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

الصورة.

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

السيارة كائن معقد يمكن إنشاؤه بمئات الطرق المختلفة، وبدلًا من حشو فئة 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)، ويستخدم الموجه كائن البنّاء في كل عمليات الإنشاء التالية، يوجد منظور مختلف لهذا يُمرَّر فيه البنّاء مباشرة إلى أسلوب الإنشاء للموجّه.
  • يمكن الحصول على نتيجة الإنشاء مباشرة من الموجّه إن كانت كل المنتجات تتبع نفس الواجهة، وإلا فإن العميل يجب أن يجلب النتيجة من البنّاء.

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

المزايا

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

العيوب

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

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