نمط Builder في Go
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 80 من 107.
يفصل نمط Builder بين بناء كائن معقّد وتمثيله. وعلى خلاف نمط الخيارات الوظيفية، يستخدم Builder بنية مخصّصة باسم builder، مع أساليب تهيّئ الكائن خطوةً بخطوة.
إليك كيفية تنفيذ 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
}يعيد كل method مؤشر builder، مما يتيح chaining للـ method. تعمل method Build() على إنهاء وإرجاع الكائن constructed:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()يتفوّق نمط Builder عندما تتطلب الكائنات خطوات تهيئة متعددة أو عندما تريد فرض تسلسل إنشاء محدد. وهو مفيد بشكل خاص لإنشاء كائنات غير قابلة للتغيير أو عندما تحتاج عملية الإنشاء نفسها إلى التحقق.
التحدي
سهللنَبْنِ مُنشئ رسائل email باستخدام نمط Builder! تحتوي رسائل email على العديد من المكوّنات الاختيارية، مثل المستلمين وCC وsubject وbody والمرفقات، مما يجعلها مرشّحًا مثاليًا للإنشاء خطوةً بخطوة باستخدام method chaining.
ستنظّم شيفرتك عبر ملفين:
email.go: عرّف بنية email وbuilder الخاص بها.أنشئ بنية
Emailمع هذه الحقول:from(string)to(slice of strings)cc(slice of strings)subject(string)body(string)
أنشئ بنية
EmailBuilderتحتوي على pointer إلى Email قيد الإنشاء. نفّذNewEmailBuilder()التي تُرجع builder جديدًا مع Email مُهيّأ (شرائح empty لكلٍّ منtoوcc).أضف methods الـbuilder التالية، بحيث تُرجع كل واحدة منها
*EmailBuilderلإتاحة chaining:From(address string)- تضبط المرسلTo(address string)- تضيف recipient إلى شريحةtoCC(address string)- تضيف recipient إلى شريحةccSubject(subject string)- تضبط سطر subjectBody(body string)- تضبط body الخاص بـemail
أضف method باسم
Build()تُرجع*Emailالمُنشأ.Finally، أضف method باسم
Summary()إلى Email، بحيث تُرجع string بهذا التنسيق:From: [from] To: [comma-separated to addresses] CC: [comma-separated cc addresses or "none"] Subject: [subject] Body: [body]main.go: أنشئ رسائل email واعرضها باستخدام builder.اقرأ عنوان المرسل، ثم اقرأ number المستلمين وعنوان كل recipient. بعد ذلك، اقرأ number مستلمي CC وعنوان كل واحد منهم. Finally، اقرأ subject وbody.
استخدم EmailBuilder مع method chaining لإنشاء email، ثم اطبع ملخّصه.
ستُزوَّد بالمدخلات التالية:
- السطر 1: عنوان المرسل
- السطر 2: number المستلمين
- الأسطر التالية: عنوان كل recipient
- السطر التالي: number مستلمي CC
- الأسطر التالية: عنوان كل مستلم CC
- السطر التالي: subject
- السطر الأخير: body
على سبيل المثال، عند إعطاء:
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.لاحظ كيف يتيح لك builder إضافة عدة مستلمين واحدًا تلو الآخر من خلال استدعاءات To() المتكررة، وكيف يحافظ النمط على كون عملية الإنشاء نظيفة وسهلة القراءة حتى مع وجود العديد من الحقول الاختيارية.
جرّب بنفسك
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 مع تسلسل الطرق لإنشاء البريد الإلكتروني
// ابدأ بـ NewEmailBuilder()، ثم سلسل From()، To()، CC()، Subject()، Body()
// أخيرًا استدعِ Build() للحصول على Email
// TODO: اطبع ملخص البريد الإلكتروني باستخدام الطريقة Summary()
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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تدرّب بنفسك: مترجم Go عبر الإنترنت