Builder — نمط إنشائي يسمح بإنشاء كائنات معقدة خطوة بخطوة. على عكس المنشئ بعشرات المعاملات، يقوم Builder بتجميع الكائن عبر سلسلة من الاستدعاءات، كل منها يهيئ حقلاً واحداً. النمط مفيد بشكل خاص للكائنات ذات المعاملات الاختيارية المتعددة: تهيئة عميل الشبكة، إعدادات قاعدة البيانات، بناة التنبيهات والتنقل. للمزيد على Refactoring Guru: Builder.
النقاط الرئيسية
Builder — نمط إنشائي من GoF يفصل بناء كائن معقد عن تمثيله. يمكن لنفس عملية البناء إنشاء تمثيلات مختلفة. Builder مفيد عندما يكون للكائن العديد من المعاملات الاختيارية ويكون المنشئ بعشرة حقول غير قابل للقراءة وغير مرن. يحل النمط أيضاً مشكلة Telescoping Constructor — النمط المعاكس حيث ينمو عدد تحميلات المنشئ بشكل أسي.
هيكل Builder يتضمن فئة داخلية ثابتة Builder بحقول تعكس حقول الفئة الرئيسية. كل set-طريقة تعيد Builder (this) للربط المتدفق. الطريقة النهائية build() تنشئ الكائن الهدف بتمرير قيم الحقول إلى منشئ خاص. الفئة الرئيسية لها منشئ خاص يقبل Builder. العميل: Object.builder().setField1(val1).setField2(val2).build().
متى نستخدم Builder — كائنات بها 5+ حقول حيث 2-3 فقط إلزامية. كائنات التهيئة (RequestConfig, DatabaseConfig). كائنات بمنطق تحقق معقد أثناء الإنشاء. كائنات يجب أن تكون ثابتة (immutable) بعد الإنشاء. في Android، يستخدم Builder بنشاط في SDK: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Builder في Kotlin له نهجان: Builder كلاسيكي على نمط Java (عبر فئة متداخلة) وBuilder بنمط DSL في Kotlin (عبر لامدا مع مستقبل). Builder بنمط Java مفضل للتوافق مع Android وعند الاستخدام مع كود Java. DSL builder هو أسلوب Kotlin البديهي: دالة تقبل لامدا داخلها this هو سياق Builder حيث يمكن تعيين الحقول مباشرة.
// Builder كلاسيكي
data class HttpConfig private constructor(
val baseUrl: String,
val timeout: Long = 30_000,
val retries: Int = 3,
val headers: Map<String, String> = emptyMap()
) {
class Builder {
private var baseUrl: String = ""
private var timeout: Long = 30_000
private var retries: Int = 3
private var headers: MutableMap<String, String> = mutableMapOf()
fun baseUrl(url: String) = apply { this.baseUrl = url }
fun timeout(ms: Long) = apply { this.timeout = ms }
fun retries(n: Int) = apply { this.retries = n }
fun header(key: String, value: String) = apply { headers[key] = value }
fun build(): HttpConfig {
require(baseUrl.isNotBlank()) { "baseUrl is required" }
return HttpConfig(baseUrl, timeout, retries, headers)
}
}
}
// الاستخدام
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — بديل بدون فئة متداخلة. دالة builder تقبل لامدا في سياق كائن البناء. هذا بديهي لـ Kotlin ولا يتطلب write-fields. تستخدم DSL builders بنشاط في Ktor Client, kotlinx.serialization, Compose (Modifier). DSL builder غير متوافق مع Java وغير مناسب للمكتبات ذات واجهة Java.
Builder في Swift — Swift ليس لديها نمط Builder مدمج، لكن الواجهة المتدفقة تُنفَّذ بسهولة عبر دوال تعيد Self. كل دالة تهيئ خاصية وتعيد self. على عكس Kotlin، Swift لا تتطلب فئة Builder منفصلة — يمكن إرجاع الكائن نفسه إذا كان قابلاً للتغيير أثناء التجميع. للكائنات الثابتة، تُستخدم فئة Builder متداخلة بشكل مشابه لـ Kotlin.
struct NetworkRequest {
let url: String
let method: HTTPMethod
let headers: [String: String]
let body: Data?
let timeout: TimeInterval
final class Builder {
private var url: String = ""
private var method: HTTPMethod = .get
private var headers: [String: String] = [:]
private var body: Data? = nil
private var timeout: TimeInterval = 30
func withURL(_: String) -> Self { /* self */ }
func withMethod(_: HTTPMethod) -> Self { /* self */ }
func withHeader(key: String, value: String) -> Self { /* self */ }
func withBody(_: Data) -> Self { /* self */ }
func withTimeout(_: TimeInterval) -> Self { /* self */ }
func build() throws -> NetworkRequest {
guard !url.isEmpty else { throw BuilderError.missingURL }
return NetworkRequest(
url: url, method: method, headers: headers,
body: body, timeout: timeout
)
}
}
}
Result Builders — Swift 5.4 قدمت @resultBuilder — آلية لغوية للبناء التصريحي للهياكل. SwiftUI, AttributedString, SceneBuilder تستخدم result builders. هذا بديل لـ Builder الكلاسيكي: بدلاً من سلسلة set-طرق، result builder يستخدم كتلة كود بعناصر يجمعها المترجم في مصفوفة أو شجرة. @ViewBuilder في SwiftUI هو المثال الأكثر شهرة: داخل body يمكن كتابة if, switch, ForEach، ويبني المترجم View من الشروط.
Telescoping Constructor — نمط معاكس حيث تحتوي الفئة على العديد من المنشئات المحملة بشكل زائد بمجموعات مختلفة من المعاملات. على سبيل المثال، ثلاثة منشئات: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). مع زيادة المعاملات، ينمو عدد المنشئات بشكل أسي — لـ n حقلاً اختيارياً تحتاج n! تركيبة. Builder يحل هذه المشكلة بالسماح بتحديد الحقول المطلوبة فقط.
| الخاصية | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| كمية الكود | نمو أسي | نمو خطي | أدنى |
| قابلية القراءة | منخفضة (أي معامل هو أي؟) | عالية (دالة + اسم) | عالية (اسم = قيمة) |
| الثبات | ثابت | ثابت | ثابت |
| التوافق مع Java | كامل | كامل | لا شيء (Kotlin فقط) |
| التحقق | في كل منشئ | في build() — مرة واحدة | في init() |
Kotlin named arguments + قيم افتراضية — بديل أنيق لـ Builder في مشاريع Kotlin الخالصة. معاملات المنشئ لها قيم افتراضية، العميل يمرر فقط ما يحتاجه: HttpConfig(baseUrl = url, timeout = 15_000). العيب هو عدم القدرة على التحقق من الحقول الإلزامية في وقت الترجمة. Builder يوفر الحقول الإلزامية عبر منشئ Builder (baseUrl إلزامي). لمكتبات Java، يبقى Builder المعيار الفعلي.
Builder في Android SDK — أحد أكثر الأنماط شيوعاً في المكتبة القياسية. AlertDialog.Builder: new AlertDialog.Builder(context).setTitle().setMessage().setPositiveButton().create(). Retrofit.Builder: new Retrofit.Builder().baseUrl().addConverterFactory().build(). OkHttpClient.Builder: new OkHttpClient.Builder().connectTimeout().addInterceptor().build(). NotificationCompat.Builder: setContentTitle().setContentText().setSmallIcon().build().
لماذا تستخدم Google Builder — التوافق العكسي. إضافة دالة جديدة إلى Builder لا يكسر الكود الموجود. إذا استخدمت Google منشئاً بـ 20 معاملاً، كل حقل جديد يتطلب تحميلاً زائداً جديداً. Builder يسمح بإضافة set-دوال لسنوات دون تغييرات مكسرة. على سبيل المثال، NotificationCompat.Builder أضاف setBubbleMetadata() في Android 11 دون التأثير على الكود الموجود.
Builder في مكتبات Kotlin — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build()), Navigation (NavOptionsBuilder). في مشاريع Kotlin، غالباً ما يُدمج Builder مع DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). يبقى النمط ملائماً لواجهات البرمجة العامة حيث يهم التوافق العكسي والتشغيل البيني مع Java.
الأسئلة الشائعة
Builder مفرط للكائنات ذات 1-3 حقول — المنشئ العادي أو data class أوضح. كما أنه مفرط في مشاريع Kotlin دون تشغيل بيني مع Java، حيث named arguments + القيم الافتراضية تحل المهمة نفسها بشكل أبسط. Builder مبرر لـ 5+ حقول، أو تحقق معقد، أو واجهات Java حيث named arguments غير متوفرة.
Builder ينشئ كائناً معقداً واحداً خطوة بخطوة (تهيئة الحقول)، Factory ينشئ كائناً كاملاً حسب النوع أو المعاملات. Builder يجيب على سؤال «كيف نبني؟»، Factory يجيب على «ماذا ننشئ؟». غالباً ما يُدمج Builder مع Factory: Factory يختار النوع، Builder يهيئ الحقول.
في SwiftUI، دور Builder تؤديه result builders (@ViewBuilder, @SceneBuilder) ومعدِّلات View (.font(), .padding()). لا نحتاج Builder الكلاسيكي لأن SwiftUI تستخدم نهجاً تصريحياً ومعدِّلات متدفقة. لمكونات UIKit، Builder مفيد: UIAlertController, URLRequest, NSAttributedString.
Builder عادة لا يتطلب أمان الخيوط لأنه يُستخدم في خيط واحد لتجميع الكائن. إذا استخدم Builder في بيئة متعددة الخيوط (حالة نادرة)، قم بمزامنة كل set-دالة وbuild(). البديل — Immutable Builder: كل set-دالة تعيد نسخة جديدة من Builder مع الحقل المعدَّل.
Retrofit.Builder هي واجهة برمجة عامة للمكتبة يجب أن تعمل دون حاوية DI. Builder يوفر مرونة التهيئة (baseUrl, المحولات, المعترضون, محولات الاستدعاء المخصصة) دون اعتماديات على Dagger أو أطر DI أخرى. داخل التطبيق، يمكن لـ DI إنشاء Retrofit مرة واحدة عبر Builder، لكن Builder نفسه يبقى جزءاً من واجهة Retrofit العامة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.