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

نمط Functional Options

جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 79 من 107.

عند إنشاء هياكل تحتوي على العديد من حقول الإعداد الاختيارية، قد تصبح دوال المُنشئ معقدة وغير عملية. يحل نمط الخيارات الوظيفية هذه المشكلة بأناقة من خلال استخدام الدوال لإعداد الكائنات.

تخيّل بنية خادم تحتوي على إعدادات اختيارية متعددة. بدلًا من مُنشئ ذي العديد من المعلمات، نُعرّف نوع option ودوال تُعيد خيارات:

type Server struct {
    host    string
    port    int
    timeout int
}

type Option func(*Server)

func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

func WithTimeout(timeout int) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

يقبل المُنشئ قائمة متغيرة الطول من options ويطبّق كل خيار منها:

func NewServer(host string, opts ...Option) *Server {
    s := &Server{
        host:    host,
        port:    8080,    // افتراضي
        timeout: 30,      // افتراضي
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

أصبح إنشاء الخوادم الآن واضحًا ومرنًا:

// استخدم الإعدادات الافتراضية
s1 := NewServer("localhost")

// خصص خيارات محددة
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))

يتألق هذا النمط لأن المستدعين يحددون فقط ما يحتاجون إليه، وتكون defaults واضحة في مكان واحد، كما أن إضافة options جديدة لا تعطل الشيفرة الموجودة. ويُستخدم على نطاق واسع في مكتبات Go مثل grpc وzap.

challenge icon

التحدي

سهل

لنبنِ اتصالًا قابلًا للتهيئة بقاعدة بيانات باستخدام نمط الخيارات الوظيفية! يتألق هذا النمط عندما تكون لديك إعدادات اختيارية كثيرة مع قيم افتراضية منطقية، وهذا بالضبط ما يحتاجه اتصال قاعدة البيانات.

ستنظّم الكود الخاص بك عبر ملفين:

  • database.go: عرّف نوع اتصال قاعدة البيانات ودوال الخيار الخاصة به.

    أنشئ بنية DBConnection بهذه الحقول غير المُصدَّرة:

    • host (سلسلة نصية)
    • port (عدد صحيح)
    • username (سلسلة نصية)
    • password (سلسلة نصية)
    • maxConnections (عدد صحيح)
    • timeout (عدد صحيح) - بالثواني

    عرّف نوع Option على أنه دالة تعدّل *DBConnection.

    أنشئ دوال الخيار التالية التي تُرجع قيمة من نوع Option:

    • WithPort(port int)
    • WithCredentials(username, password string)
    • WithMaxConnections(max int)
    • WithTimeout(seconds int)

    أنشئ مُنشئًا NewDBConnection(host string, opts ...Option) يُرجع قيمة من النوع *DBConnection. عيّن القيم الافتراضية التالية: المنفذ 5432، واسم المستخدم "admin"، وكلمة المرور "secret"، وmaxConnections 10، وtimeout 30. طبّق جميع الخيارات المقدَّمة بعد تعيين القيم الافتراضية.

    أضف طريقة ConnectionString() تُرجع معلومات الاتصال بهذا التنسيق:

    [username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)
  • main.go: أنشئ اتصالات بقاعدة البيانات باستخدام إعدادات متنوعة.

    اقرأ المضيف، ثم اقرأ عدد الخيارات التي يجب تطبيقها. لكل خيار، اقرأ نوع الخيار وقيمته أو قيمه:

    • port متبوعًا برقم المنفذ
    • credentials متبوعًا باسم المستخدم وكلمة المرور (سطران)
    • maxconn متبوعًا بعدد الاتصالات الأقصى
    • timeout متبوعًا بمهلة الانتظار بالثواني

    أنشئ قيمة DBConnection بالخيارات المحددة واطبع سلسلة الاتصال الخاصة بها.

ستُقدَّم المدخلات التالية:

  • السطر 1: اسم المضيف
  • السطر 2: عدد الخيارات
  • الأسطر التالية: نوع الخيار وقيمته أو قيمه

على سبيل المثال، عند إعطاء:

localhost
0

يجب أن يكون الناتج:

admin:secret@localhost:5432 (max:10, timeout:30s)

وعند إعطاء:

db.example.com
2
port
3306
credentials
root
mypassword

يجب أن يكون الناتج:

root:mypassword@db.example.com:3306 (max:10, timeout:30s)

وعند إعطاء:

production.server
4
port
5433
credentials
dbuser
securepass123
maxconn
50
timeout
60

يجب أن يكون الناتج:

dbuser:securepass123@production.server:5433 (max:50, timeout:60s)

لاحظ أن المستدعين يحددون الخيارات التي يحتاجون إليها فقط؛ أما الإعدادات غير المحددة فتستخدم قيمًا افتراضية منطقية. هذه هي أناقة نمط الخيارات الوظيفية!

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// اقرأ المضيف
	host, _ := reader.ReadString('\n')
	host = strings.TrimSpace(host)

	// اقرأ عدد الخيارات
	numOptionsStr, _ := reader.ReadString('\n')
	numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))

	// اجمع الخيارات
	var opts []Option

	for i := 0; i < numOptions; i++ {
		optionType, _ := reader.ReadString('\n')
		optionType = strings.TrimSpace(optionType)

		switch optionType {
		case "port":
			portStr, _ := reader.ReadString('\n')
			port, _ := strconv.Atoi(strings.TrimSpace(portStr))
			// TODO: أضف خيار WithPort إلى opts
			_ = port // احذف هذا السطر عند التنفيذ

		case "credentials":
			username, _ := reader.ReadString('\n')
			username = strings.TrimSpace(username)
			password, _ := reader.ReadString('\n')
			password = strings.TrimSpace(password)
			// TODO: أضف خيار WithCredentials إلى opts
			_, _ = username, password // احذف هذا السطر عند التنفيذ

		case "maxconn":
			maxStr, _ := reader.ReadString('\n')
			maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
			// TODO: أضف خيار WithMaxConnections إلى opts
			_ = maxConn // احذف هذا السطر عند التنفيذ

		case "timeout":
			timeoutStr, _ := reader.ReadString('\n')
			timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
			// TODO: أضف خيار WithTimeout إلى opts
			_ = timeout // احذف هذا السطر عند التنفيذ
		}
	}

	// TODO: أنشئ DBConnection باستخدام NewDBConnection مع host و opts
	// TODO: اطبع سلسلة الاتصال باستخدام طريقة ConnectionString()

	_ = opts // احذف هذا السطر عند التنفيذ
	fmt.Println("TODO: Print connection string here")
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Go عبر الإنترنت