نمط 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.
التحدي
سهللنبنِ اتصالًا قابلًا للتهيئة بقاعدة بيانات باستخدام نمط الخيارات الوظيفية! يتألق هذا النمط عندما تكون لديك إعدادات اختيارية كثيرة مع قيم افتراضية منطقية، وهذا بالضبط ما يحتاجه اتصال قاعدة البيانات.
ستنظّم الكود الخاص بك عبر ملفين:
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"، وmaxConnections10، وtimeout30. طبّق جميع الخيارات المقدَّمة بعد تعيين القيم الافتراضية.أضف طريقة
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")
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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 عبر الإنترنت