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

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

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

للباني ذي المعاملات الكثيرة عيب أيضاً: فليست كل المعاملات مطلوبة في جميع الحالات.
في معظم الحالات، ستبقى أغلب المعاملات غير مستخدمة، مما يجعل استدعاءات المُنشئ قبيحةً للغاية. فعلى سبيل المثال، لا يمتلك إلا جزء صغير من المنازل حمامات سباحة، لذا ستكون المعاملات المتعلقة بحمامات السباحة عديمة الفائدة في تسعة أحيان من أصل عشرة.
##الحل
يقترح نمط البنّاء أن تستخرج شيفرة إنشاء الكائن من فئته الخاصة وتنقلها إلى كائنات منفصلة تُسمى البنّاؤون.
ينظّم النمط بناء الكائن في مجموعة من الخطوات (buildWalls, buildDoor، إلخ). لإنشاء كائن، تنفّذ سلسلة من هذه الخطوات على كائن بنّاء. والمهم أنك لا تحتاج إلى استدعاء جميع الخطوات؛ يكفي استدعاء الخطوات اللازمة لإنتاج التركيب المحدد للكائن.
قد تستلزم بعض خطوات البناء تطبيقًا مختلفًا عندما تحتاج إلى بناء تمثيلات مختلفة للمنتج. على سبيل المثال، يمكن بناء جدران الكوخ من الخشب، لكن جدران القلعة يجب أن تُبنى من الحجر.
في هذه الحالة، يمكنك إنشاء عدة فئات بنّاء مختلفة تُطبّق نفس مجموعة خطوات البناء لكن بطريقة مختلفة. ثم يمكنك استخدام هؤلاء البنّاؤين في عملية البناء (أي مجموعة مرتبة من استدعاءات خطوات البناء) لإنتاج أنواع مختلفة من الكائنات.

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

يعرف المُوجّه خطوات البناء التي ينبغي تنفيذها للحصول على منتج صالح للعمل.
امتلاك فئة مُوجّه في برنامجك ليس أمرًا إلزاميًا بالضرورة. يمكنك دائمًا استدعاء خطوات البناء بترتيب محدد مباشرةً من كود العميل. ومع ذلك، قد تكون فئة المُوجّه مكانًا مثاليًا لوضع روتينات البناء المختلفة حتى تتمكن من إعادة استخدامها في أنحاء برنامجك.
علاوةً على ذلك، تُخفي فئة المُوجّه تفاصيل إنشاء المنتج تمامًا عن كود العميل. لا يحتاج العميل إلا إلى ربط بنّاء بمُوجّه، وإطلاق عملية البناء مع المُوجّه، واستلام النتيجة من البنّاء.
##البنية
-
تُعلن واجهة Builder عن خطوات إنشاء المنتج المشتركة بين جميع أنواع البنّاؤين.
-
تُوفّر البنّاؤون المحددون تطبيقات مختلفة لخطوات البناء. قد يُنتج البنّاؤون المحددون منتجات لا تتبع الواجهة المشتركة.
-
المنتجات هي الكائنات الناتجة. لا يجب أن تنتمي المنتجات التي يُنشئها بنّاؤون مختلفون إلى نفس التسلسل الهرمي للفئات أو الواجهة.
-
تُعرّف فئة المُوجّه الترتيب الذي يجب استدعاء خطوات البناء به، بحيث يمكنك إنشاء تهيئات محددة للمنتجات وإعادة استخدامها.
-
يجب على العميل ربط أحد كائنات البنّاء بالمُوجّه. عادةً ما يتم ذلك مرةً واحدة فقط، عبر معاملات مُنشئ المُوجّه. ثم يستخدم المُوجّه كائن البنّاء هذا في جميع عمليات البناء اللاحقة. ومع ذلك، هناك نهج بديل عندما يُمرّر العميل كائن البنّاء إلى تابع الإنتاج الخاص بالمُوجّه. في هذه الحالة، يمكنك استخدام بنّاء مختلف في كل مرة تُنتج فيها شيئًا مع المُوجّه.
##الكود الزائف
يُوضّح هذا المثال على نمط البنّاء كيف يمكنك إعادة استخدام نفس شيفرة إنشاء الكائن عند بناء أنواع مختلفة من المنتجات، كالسيارات، وإنشاء الأدلة المقابلة لها.
السيارة كائنٌ معقد يمكن بناؤه بمئة طريقة مختلفة. بدلًا من تضخيم فئة Car بمُنشئ ضخم، استخرجنا شيفرة تجميع السيارة إلى فئة بنّاء سيارة منفصلة. تحتوي هذه الفئة على مجموعة من التوابع لتهيئة أجزاء مختلفة من السيارة.
إذا احتاج كود العميل إلى تجميع موديل سيارة خاص ومُعدَّل بدقة، فيمكنه العمل مع البنّاء مباشرةً. من ناحية أخرى، يمكن للعميل تفويض التجميع إلى فئة المُوجّه التي تعرف كيفية استخدام البنّاء لبناء عدة موديلات من أكثر السيارات شيوعًا.
قد تُصاب بالدهشة، لكن كل سيارة تحتاج إلى دليل (بجدية، من يقرأها؟). يصف الدليل كل مميزات السيارة، لذا تتفاوت التفاصيل في الأدلة عبر الموديلات المختلفة. لهذا السبب من المنطقي إعادة استخدام عملية بناء موجودة لكل من السيارات الفعلية وأدلتها المقابلة. بالطبع، بناء الدليل ليس كبناء السيارة، ولهذا يجب علينا توفير فئة بنّاء أخرى متخصصة في تأليف الأدلة. تُطبّق هذه الفئة نفس توابع البناء الخاصة بأختها منشئة السيارات، لكنها بدلًا من صنع أجزاء السيارة، تصفها. بتمرير هؤلاء البنّاؤين إلى نفس كائن المُوجّه، يمكننا بناء إما سيارة أو دليل.
الخطوة الأخيرة هي الحصول على الكائن الناتج. فرغم الارتباط بين السيارة المعدنية والدليل الورقي، فإنهما شيئان مختلفان تماماً. لا يمكننا وضع أسلوب للحصول على النتائج في المُوجّه دون ربط المُوجّه بفئات المنتجات الملموسة. لذلك نحصل على نتيجة البناء من البنّاء الذي نفّذ العمل.
// استخدام نمط Builder منطقي فقط عندما تكون منتجاتك
// معقدةً جدًا وتتطلب تهيئةً مكثفة. المنتجان
// التاليان مترابطان وإن لم تكن لهما
// واجهة مشتركة.
class Car is
// يمكن أن تحتوي السيارة على GPS وحاسوب رحلات وعدد
// من المقاعد. قد تحتوي موديلات مختلفة من السيارات (رياضية، SUV،
// مكشوفة) على مميزات مختلفة مثبّتة أو
// مفعّلة.
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
// تركيب نظام تحديد المواقع العالمي.
// من المفترض أن يوفر البنّاؤون المحددون توابعهم الخاصة
// لاسترداد النتائج. لأن أنواعًا مختلفة من البنّاؤين قد تُنشئ
// منتجات مختلفة تمامًا لا تتبع جميعها نفس الواجهة.
// لذلك لا يمكن الإعلان عن هذه التوابع في واجهة البنّاء
// (على الأقل ليس في لغات البرمجة ذات الأنواع الستاتيكية).
//
// عادةً، بعد إعادة النتيجة النهائية إلى العميل، يُتوقع من
// نموذج البنّاء أن يكون جاهزًا لبدء إنتاج منتج آخر. لهذا
// السبب من الشائع استدعاء تابع reset في نهاية جسم
// تابع `getProduct`. ومع ذلك، هذا السلوك
// ليس إلزاميًا، ويمكنك جعل البنّاء ينتظر
// استدعاء reset صريحًا من كود العميل قبل التخلص من النتيجة السابقة.
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
// إضافة تعليمات نظام تحديد المواقع.
method getProduct():Manual is
// إعادة الدليل وإعادة تعيين البنّاء.
// المُوجّه مسؤول فقط عن تنفيذ خطوات البناء
// بتسلسل معين. يفيد ذلك عند إنتاج منتجات
// وفق ترتيب أو تهيئة محددة. بالمعنى الدقيق،
// فئة المُوجّه اختيارية، إذ يمكن للعميل التحكم
// في البنّاؤين مباشرةً.
class Director is
// يعمل المُوجّه مع أي نموذج بنّاء يُمرّره
// إليه كود العميل. بهذه الطريقة يمكن لكود العميل
// تغيير النوع النهائي للمنتج المُجمَّع حديثًا.
// يمكن للمُوجّه بناء عدة تنوعات من المنتج
// باستخدام نفس خطوات البناء.
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
// ...
// يُنشئ كود العميل كائن البنّاء ويُمرّره إلى المُوجّه
// ثم يُطلق عملية البناء. يُسترد
// الناتج النهائي من كائن البنّاء.
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()
##متى يُستخدم
استخدم نمط البنّاء للتخلص من «المُنشئ المتتالي».
لنفترض أن لديك مُنشئًا يحتوي على عشرة معاملات اختيارية. استدعاء مثل هذا المُنشئ الضخم أمرٌ مزعج للغاية؛ لذا تلجأ إلى التحميل الزائد للمُنشئ وإنشاء نسخ أقصر منه بمعاملات أقل. لا تزال هذه المُنشئات تُشير إلى المُنشئ الرئيسي، حيث تُمرّر قيمًا افتراضية لأي معاملات محذوفة.
class Pizza {
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
// ...
لا يمكن إنشاء مثل هذا الباني الضخم إلا في اللغات التي تدعم التحميل الزائد للأساليب، مثل C# أو Java.
يُتيح لك نمط البنّاء بناء الكائنات خطوةً بخطوة، مستعينًا فقط بالخطوات التي تحتاجها فعلًا. بعد تطبيق النمط، لن تضطر بعد الآن إلى حشو عشرات المعاملات في مُنشئاتك.
استخدم نمط البنّاء عندما تريد أن تتمكن شيفرتك من إنشاء تمثيلات مختلفة لمنتجٍ ما (على سبيل المثال، منازل حجرية ومنازل خشبية).
يمكن تطبيق نمط البنّاء عندما يتضمّن بناء تمثيلات مختلفة للمنتج خطواتٍ متشابهة لا تختلف إلا في التفاصيل.
تُعرّف واجهة البنّاء الأساسية جميع خطوات البناء الممكنة، وتُطبّق البنّاؤون المحددون هذه الخطوات لبناء تمثيلات معيّنة للمنتج. وفي الوقت ذاته، تُوجّه فئة المُوجّه ترتيب البناء.
استخدم البنّاء لبناء أشجار المركّب أو غيرها من الكائنات المعقدة.
يُتيح لك نمط البنّاء إنشاء المنتجات خطوةً بخطوة. يمكنك تأجيل تنفيذ بعض الخطوات دون الإخلال بالمنتج النهائي. بل يمكنك حتى استدعاء الخطوات بشكل تعاودي، وهو ما يفيدك عند الحاجة إلى بناء شجرة كائنات.
لا يكشف البنّاء عن المنتج غير المكتمل أثناء تنفيذ خطوات البناء. هذا يمنع كود العميل من استرداد نتيجة غير مكتملة.
##كيفية التنفيذ
-
تأكد من قدرتك على تحديد خطوات البناء المشتركة بوضوح لبناء جميع تمثيلات المنتج المتاحة. وإلا لن تتمكن من المضي قدمًا في تطبيق النمط.
-
أعلن عن هذه الخطوات في واجهة البنّاء الأساسية.
-
أنشئ فئة بنّاء محدد لكل تمثيل من تمثيلات المنتج ونفّذ خطوات البناء الخاصة بها.
لا تنسَ تطبيق تابع لاسترداد نتيجة البناء. السبب في عدم إمكانية الإعلان عن هذا التابع داخل واجهة البنّاء هو أن البنّاؤين المختلفين قد يُنشئون منتجات لا تشترك في واجهة مشتركة. لذلك لا تعرف ما سيكون نوع الإرجاع لهذا التابع. ومع ذلك، إن كنت تتعامل مع منتجات من تسلسل هرمي واحد، يمكن إضافة تابع الاسترداد بأمان إلى الواجهة الأساسية.
-
فكّر في إنشاء فئة مُوجّه. قد تُغلّف طرقًا مختلفة لبناء المنتج باستخدام نفس كائن البنّاء.
-
يُنشئ كود العميل كلًا من كائن البنّاء وكائن المُوجّه. قبل بدء البناء، يجب على العميل تمرير كائن البنّاء إلى المُوجّه. عادةً ما يتم ذلك مرةً واحدة فقط، عبر معاملات مُنشئ فئة المُوجّه. يستخدم المُوجّه كائن البنّاء في جميع عمليات البناء اللاحقة. هناك نهج بديل يتمثل في تمرير البنّاء إلى تابع بناء منتج محدد في المُوجّه.
-
لا يمكن الحصول على نتيجة البناء مباشرةً من المُوجّه إلا إذا كانت جميع المنتجات تتبع نفس الواجهة. وإلا يجب على العميل استرداد النتيجة من البنّاء.
##المزايا والعيوب
- يمكنك إنشاء الكائنات خطوةً بخطوة وتأجيل خطوات البناء أو تشغيلها بشكل تعاودي.
- يمكنك إعادة استخدام نفس شيفرة البناء عند إنشاء تمثيلات مختلفة للمنتجات.
- مبدأ المسؤولية الواحدة. يمكنك عزل شيفرة البناء المعقدة عن منطق الأعمال الخاص بالمنتج.
- تزداد التعقيدية الكلية للشيفرة نظرًا لأن النمط يستلزم إنشاء عدة فئات جديدة.
##العلاقات مع الأنماط الأخرى
-
تبدأ كثير من التصاميم باستخدام Factory Method (الأقل تعقيدًا والأكثر قابلية للتخصيص عبر الفئات الفرعية) وتتطور نحو Abstract Factory أو Prototype أو Builder (الأكثر مرونةً لكن الأكثر تعقيدًا).
-
يُركّز Builder على إنشاء كائنات معقدة خطوةً بخطوة. يتخصص Abstract Factory في إنشاء عائلات من الكائنات المترابطة. يُعيد Abstract Factory المنتج فورًا، في حين يُتيح لك Builder تنفيذ خطوات بناء إضافية قبل استرداد المنتج.
-
يمكنك استخدام Builder عند إنشاء أشجار Composite معقدة لأنك تستطيع برمجة خطوات البناء الخاصة به للعمل بشكل تعاودي.
-
يمكنك دمج Builder مع Bridge: تضطلع فئة المُوجّه بدور التجريد، في حين تعمل البنّاؤون المختلفون كتطبيقات.
-
يمكن تطبيق Abstract Factories وBuilders وPrototypes جميعها كـSingletons.