نمط Builder في Go
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 80 من 107.
يفصل Builder Pattern عملية إنشاء كائن معقد عن تمثيله. وعلى عكس نمط Functional Options، يستخدم Builder الـ builder struct مخصصاً مع methods تقوم بتهيئة الكائن خطوة بخطوة.
إليك كيفية تنفيذ نمط البناء (builder) لطلب HTTP:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}
func (b *RequestBuilder) Build() *Request {
return b.request
}تعيد كل دالة مؤشر الباني (builder pointer)، مما يسمح بتسلسل الدوال (method chaining). تقوم الدالة Build() بإنهاء وإرجاع الكائن الذي تم إنشاؤه:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()يتفوق نمط الباني (Builder pattern) عندما تتطلب الكائنات خطوات تهيئة متعددة أو عندما تريد فرض تسلسل بناء محدد. وهو مفيد بشكل خاص لإنشاء كائنات غير قابلة للتغيير (immutable objects) أو عندما تحتاج عملية البناء نفسها إلى التحقق من الصحة.
التحدي
سهللنقم ببناء مؤلف رسائل بريد إلكتروني باستخدام نمط البناء (Builder Pattern)! تحتوي رسائل البريد الإلكتروني على العديد من المكونات الاختيارية — المستلمون، نسخة كربونية (CC)، الموضوع، نص الرسالة، المرفقات — مما يجعلها مرشحاً مثالياً للبناء خطوة بخطوة مع تسلسل الأساليب (method chaining).
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
email.go: حدد هيكل البريد الإلكتروني والباني (builder) الخاص به.أنشئ هيكل
Emailمع هذه الحقول:from(string)to(شريحة من السلاسل النصية slice of strings)cc(شريحة من السلاسل النصية slice of strings)subject(string)body(string)
أنشئ هيكل
EmailBuilderالذي يحمل مؤشراً (pointer) إلى Email قيد الإنشاء. قم بتنفيذNewEmailBuilder()الذي يعيد بانياً جديداً مع Email مهيأ (شرائح فارغة لـtoوcc).أضف أساليب البناء هذه، حيث يعيد كل منها
*EmailBuilderمن أجل التسلسل:From(address string)- يحدد المرسلTo(address string)- يضيف مستلماً إلى شريحةtoCC(address string)- يضيف مستلماً إلى شريحةccSubject(subject string)- يحدد سطر الموضوعBody(body string)- يحدد نص البريد الإلكتروني
أضف أسلوب
Build()الذي يعيد الـ*Emailالذي تم بناؤه.أخيراً، أضف أسلوب
Summary()على Email يعيد سلسلة نصية بهذا التنسيق:From: [from] To: [comma-separated to addresses] CC: [comma-separated cc addresses or "none"] Subject: [subject] Body: [body]main.go: قم ببناء وعرض رسائل البريد الإلكتروني باستخدام الباني.اقرأ عنوان المرسل، ثم اقرأ عدد المستلمين وكل عنوان مستلم. بعد ذلك، اقرأ عدد مستلمي النسخة الكربونية (CC) وكل عنوان CC. أخيراً، اقرأ الموضوع ونص الرسالة.
استخدم EmailBuilder مع تسلسل الأساليب لبناء البريد الإلكتروني، ثم اطبع ملخصه.
سيتم توفير المدخلات التالية:
- السطر 1: عنوان المرسل
- السطر 2: عدد المستلمين
- الأسطر التالية: كل عنوان مستلم
- السطر التالي: عدد مستلمي النسخة الكربونية (CC)
- الأسطر التالية: كل عنوان CC
- السطر التالي: الموضوع
- السطر الأخير: نص الرسالة
على سبيل المثال، إذا كان المدخل:
alice@company.com
2
bob@company.com
carol@company.com
1
manager@company.com
Weekly Report
Please find the weekly report attached.يجب أن يكون مخرجك:
From: alice@company.com
To: bob@company.com, carol@company.com
CC: manager@company.com
Subject: Weekly Report
Body: Please find the weekly report attached.وإذا كان المدخل:
support@service.com
1
customer@email.com
0
Your Ticket Update
Your support ticket has been resolved.يجب أن يكون مخرجك:
From: support@service.com
To: customer@email.com
CC: none
Subject: Your Ticket Update
Body: Your support ticket has been resolved.لاحظ كيف يسمح لك الباني بإضافة مستلمين متعددين واحداً تلو الآخر من خلال استدعاءات To() المتكررة، وكيف يحافظ هذا النمط على عملية البناء نظيفة وقابلة للقراءة حتى مع وجود العديد من الحقول الاختيارية.
ورقة مرجعية
يفصل نمط الباني (Builder Pattern) عملية إنشاء كائن معقد عن تمثيله باستخدام هيكل (struct) باني مخصص مع طرق (methods) تقوم بتهيئة الكائن خطوة بخطوة.
الهيكل الأساسي:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}تعيد طرق الباني مؤشر الباني لتمكين تسلسل الطرق (method chaining):
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}تقوم الطريقة Build() بإنهاء وإرجاع الكائن الذي تم إنشاؤه:
func (b *RequestBuilder) Build() *Request {
return b.request
}الاستخدام مع تسلسل الطرق:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()يكون نمط الباني مفيداً عندما تتطلب الكائنات خطوات تهيئة متعددة، أو عند فرض تسلسل إنشاء محدد، أو لإنشاء كائنات غير قابلة للتغيير (immutable)، أو عندما تحتاج عملية الإنشاء إلى التحقق من الصحة (validation).
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// قراءة عنوان المرسل
from, _ := reader.ReadString('\n')
from = strings.TrimSpace(from)
// قراءة عدد المستلمين
toCountStr, _ := reader.ReadString('\n')
toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))
// قراءة عنوان كل مستلم
toAddresses := make([]string, toCount)
for i := 0; i < toCount; i++ {
addr, _ := reader.ReadString('\n')
toAddresses[i] = strings.TrimSpace(addr)
}
// قراءة عدد مستلمي نسخة (CC)
ccCountStr, _ := reader.ReadString('\n')
ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))
// قراءة كل عنوان نسخة (CC)
ccAddresses := make([]string, ccCount)
for i := 0; i < ccCount; i++ {
addr, _ := reader.ReadString('\n')
ccAddresses[i] = strings.TrimSpace(addr)
}
// قراءة الموضوع
subject, _ := reader.ReadString('\n')
subject = strings.TrimSpace(subject)
// قراءة نص الرسالة
body, _ := reader.ReadString('\n')
body = strings.TrimSpace(body)
// TODO: استخدم EmailBuilder مع تسلسل الدوال (method chaining) لإنشاء البريد الإلكتروني
// ابدأ بـ NewEmailBuilder()، ثم اربط From()، و To()، و CC()، و Subject()، و Body()
// وأخيرًا استدعِ Build() للحصول على Email
// TODO: اطبع ملخص البريد الإلكتروني باستخدام الدالة Summary()
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool12مفاهيم OOP المتقدمة
نمط Functional Optionsنمط Builder في Goتسلسل الدوال (Method Chaining)الأسماء المستعارة للأنواع مقابل التعريفاتأساسيات الـ Reflectionنظرة عامة على Code Generation