نمط البنّاء (Builder)
نمط البنّاء (Builder)
يفصل نمط البنّاء (Builder) عملية بناء الكائن المعقّد عن تمثيله، مما يسمح لنفس عملية البناء بإنتاج نتائج مختلفة. في Java الحديثة يُعدّ هذا النمط الحلّ القياسي لبناء كائنات قيمة ثابتة (Immutable) ذات حقول اختيارية كثيرة — تلك الحالات التي تتحوّل فيها المُنشئات المتسلسلة إلى شيفرة غير مقروءة ويمنع فيها استخدام setters تحقيق الثبات.
لماذا لا نكتفي بالمُنشئات أو الـ Setters؟
تخيّل كلاس HttpRequest يحتوي على حقل URL مطلوب وحقل method مطلوب وثمانية حقول اختيارية (headers، body، timeout، follow-redirects، auth، proxy، cache policy، retry count). هناك نهجان شائعان لكنّهما معيبان:
- المُنشئات المتسلسلة (Telescoping constructors) — تكتب مُنشئًا لكل تركيبة ممكنة. مع ثمانية حقول اختيارية تنتهي بعشرات المُنشئات، وأماكن الاستدعاء مثل
new HttpRequest(url, method, null, null, 30, true, null, null, 3)تصبح مستحيلة القراءة. - أسلوب JavaBean (باستخدام setters قابلة للتعديل) — يبدأ الكائن في حالة غير متسقة، ولا يمكن جعله
final/ ثابتًا، وسلامة الخيوط المتزامنة تستلزم تزامنًا خارجيًا.
يحلّ نمط Builder كلا المشكلتين: توفّر الواجهة البرمجية السلسة (Fluent API) أماكن استدعاء تشرح نفسها بنفسها، ويُجمَّع الكائن الهدف مرة واحدة فقط — عند استدعاء build() — فيمكن جعله ثابتًا بالكامل.
الشكل الكلاسيكي: Builder داخلي ساكن
الأسلوب القياسي في Java هو وضع public static final class Builder داخل الكلاس الهدف، ويكون للكلاس الهدف مُنشئ خاص لا يقبل سوى الـ Builder.
مكان الاستدعاء يُشبه جملة بشرية مفهومة:
Builder ليفرضها المصرِّف (compiler). أما الحقول الاختيارية فتحصل على قيم افتراضية وـ setters سلسة. هذا أوضح من جعل كل الحقول اختيارية والتحقق من اكتمالها في build().
التحقق داخل build()
دالة build() هي المكان الصحيح للتحقق من القيود التي تشمل أكثر من حقل واحد. أما التحقق من حقل واحد (null checks، نطاق القيم) فيجب في كل setter سلس حتى يظهر الخطأ قريبًا من الاستدعاء المعيب. مثال:
IllegalArgumentException في الـ setter فور رؤية بيانات خاطئة. إن أجّلت كل التحقق إلى build() سيشير تتبّع المكدس (stack trace) إليها لا إلى الاستدعاء المعيب الفعلي، فيطول وقت التصحيح.
Copy Builders — تحديثات ثابتة آمنة
لأن الكائن الهدف ثابت لا يمكن تعديله بعد البناء. يتيح لك نمط copy builder (يُسمى أيضًا نمط withX) اشتقاق كائن جديد من كائن موجود مع تغيير حقول بعينها فقط — نفس فكرة تعبير with في سجلات Java (records):
Builder مقابل Java Records
توفّر records في Java 16+ حاملات بيانات ثابتة مضغوطة للحالات البسيطة. استخدم Builder عندما:
- يوجد عدد كبير من الحقول الاختيارية (تمتلك السجلات مُنشئًا قياسيًا واحدًا — جميع الحقول مطلوبة).
- تحتاج إلى منطق تحقق يشمل أكثر من حقل في
build(). - الكائن جزء من واجهة برمجية سلسة (Fluent API) (بُناة الاستعلامات، عملاء HTTP، بيانات الاختبارات).
- تريد دعم copy builders بصورة أنيقة.
أما حامل البيانات المكوّن من ثلاثة حقول بدون حقول اختيارية وبدون تحقق، فالـ record أبسط وأفضل.
Lombok @Builder — متى تلجأ إليه
في قواعد الشيفرة الإنتاجية تُولّد تعليمة Lombok @Builder الـ inner Builder وقت التصريف، مما يُزيل الشيفرة المتكررة. المقايضة: الـ builder المُولَّد لا يفرض الحقول المطلوبة ولا يُجري تحققًا مخصصًا إلا بإضافة @Builder.ObtainVia وتجاوز build() يدويًا. استخدمه مع كائنات DTO / القيم المبسوطة؛ واكتبه يدويًا حين تحتاج قيودًا صارمة.
الاستخدام الفعلي في JDK والنظام البيئي
النمط موجود في كل مكان من Java الحديثة:
StringBuilder/StringJoiner— تجميع الأجزاء وإنتاجString.HttpClient.newBuilder()/HttpRequest.newBuilder()— عميل HTTP في JDK 11+.ProcessBuilder— تهيئة تشغيل عمليات نظام التشغيل.MockMvcRequestBuildersفي Spring، وCriteriaBuilderفي Hibernate، وImmutableList.builder()في Guava.
الخلاصة
نمط Builder هو الإجابة المهنية لبناء الكائنات المعقدة في Java. بتمرير الحقول المطلوبة إلى مُنشئ الـ Builder، وتوفير setters سلسة للحقول الاختيارية، وتمركز التحقق في build()، والحفاظ على ثبات الكلاس الهدف بالكامل، تحصل على أماكن استدعاء مقروءة وأمان في وقت التصريف وكائنات يسهل مشاركتها بأمان عبر الخيوط المتزامنة. امتداد copy-builder يجعل التحديثات الثابتة مريحة. في الدرس التالي ننتقل إلى الأنماط السلوكية بدءًا بنمط Strategy.