Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط 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) أو عندما تحتاج عملية البناء نفسها إلى التحقق من الصحة.

challenge icon

التحدي

سهل

لنقم ببناء مؤلف رسائل بريد إلكتروني باستخدام نمط البناء (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) - يضيف مستلماً إلى شريحة to
    • CC(address string) - يضيف مستلماً إلى شريحة cc
    • Subject(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()
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming