نمط Functional Options
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 79 من 107.
عند إنشاء هياكل (structs) تحتوي على العديد من حقول التكوين الاختيارية، يمكن أن تصبح دوال المنشئ (constructor functions) غير عملية. يحل نمط الخيارات الوظيفية (Functional Options Pattern) هذه المشكلة بأناقة من خلال استخدام الدوال لتكوين الكائنات.
ضع في اعتبارك بنية خادم (server struct) مع إعدادات اختيارية متعددة. بدلاً من استخدام منشئ (constructor) بمعلمات عديدة، نقوم بتعريف نوع خيار (option type) ودوال تُرجع خيارات:
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
}
}يقبل المنشئ قائمة متغيرة من الخيارات ويقوم بتطبيق كل منها:
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))يتألق هذا النمط لأن المستدعين يحددون فقط ما يحتاجون إليه، والقيم الافتراضية واضحة في مكان واحد، وإضافة خيارات جديدة لا تؤدي إلى كسر الكود الحالي. يُستخدم هذا النمط على نطاق واسع في مكتبات Go مثل grpc و zap.
التحدي
سهللنقم ببناء اتصال قاعدة بيانات قابل للتكوين باستخدام نمط الخيارات الوظيفية (Functional Options Pattern)! يتألق هذا النمط عندما يكون لديك العديد من الإعدادات الاختيارية مع قيم افتراضية منطقية—وهو بالضبط ما يحتاجه اتصال قاعدة البيانات.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
database.go: حدد نوع اتصال قاعدة البيانات ودوال الخيارات.قم بإنشاء هيكل
DBConnectionمع هذه الحقول غير المصدرة (unexported):host(string)port(int)username(string)password(string)maxConnections(int)timeout(int) - بالثواني
حدد نوع
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: قم ببناء اتصالات قاعدة البيانات بتكوينات متنوعة.اقرأ المضيف (host)، ثم اقرأ عدد الخيارات المراد تطبيقها. لكل خيار، اقرأ نوع الخيار وقيمته (أو قيمه):
portمتبوعاً برقم المنفذcredentialsمتبوعاً باسم المستخدم وكلمة المرور (في سطرين)maxconnمتبوعاً بعدد أقصى للاتصالاتtimeoutمتبوعاً بالمهلة الزمنية بالثواني
قم بإنشاء
DBConnectionبالخيارات المحددة واطبع سلسلة الاتصال الخاصة به.
سيتم توفير المدخلات التالية:
- السطر 1: اسم المضيف (Host name)
- السطر 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)لاحظ كيف يحدد المستدعون فقط الخيارات التي يحتاجونها—الإعدادات غير المحددة تستخدم قيمًا افتراضية منطقية. هذه هي أناقة نمط الخيارات الوظيفية!
ورقة مرجعية
يوفر نمط الخيارات الوظيفية (Functional Options Pattern) طريقة أنيقة لتهيئة الهياكل (structs) التي تحتوي على العديد من الحقول الاختيارية باستخدام الدوال بدلاً من معاملات المنشئ (constructor parameters) الطويلة.
قم بتعريف نوع Option كدالة تقوم بتعديل مؤشر (pointer) إلى الهيكل الخاص بك:
type Server struct {
host string
port int
timeout int
}
type Option func(*Server)قم بإنشاء دوال خيارات تعيد إغلاقات (closures) من نوع Option:
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout int) Option {
return func(s *Server) {
s.timeout = timeout
}
}يقبل المنشئ (constructor) قائمة متغيرة (variadic list) من الخيارات، ويقوم بتعيين القيم الافتراضية، ثم يطبق كل خيار:
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))الفوائد: القيم الافتراضية مركزية، ويحدد المستدعون فقط ما يحتاجون إليه، وإضافة خيارات جديدة لا تؤدي إلى كسر الكود الحالي.
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// قراءة host
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")
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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