الحقول المصدرة وغير المصدرة
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 47 من 107.
في لغة Go، يتم التحكم في التغليف (encapsulation) على مستوى الـ struct من خلال حالة الأحرف لأسماء الحقول. تحدد هذه القاعدة البسيطة ما إذا كان يمكن الوصول إلى الحقول من خارج الـ package التي تم تعريف الـ struct فيها.
الحقول التي تبدأ بـ حرف كبير تكون مُصدّرة—يمكن الوصول إليها من أي حزمة. الحقول التي تبدأ بـ حرف صغير تكون غير مُصدّرة—يمكن الوصول إليها فقط داخل نفس الحزمة:
// في الحزمة "user"
type User struct {
Name string // مُصدّر - يمكن الوصول إليه من كل مكان
Email string // مُصدّر - يمكن الوصول إليه من كل مكان
password string // غير مُصدّر - يمكن الوصول إليه فقط داخل الحزمة "user"
age int // غير مُصدّر - يمكن الوصول إليه فقط داخل الحزمة "user"
}
عندما تقوم حزمة أخرى باستيراد واستخدام هذا الـ struct، فإنها لا تستطيع التفاعل إلا مع الحقول المصدرة (exported fields):
// في الحزمة "main"
import "user"
func main() {
u := user.User{
Name: "Alice", // يعمل
Email: "a@mail.com", // يعمل
// password: "secret", // خطأ في التجميع: حقل غير معروف
}
fmt.Println(u.Name) // يعمل
// fmt.Println(u.password) // خطأ في التجميع: لا يمكن الرجوع إلى حقل غير مصدر
}
تحمي هذه الآلية البيانات الحساسة وتفاصيل التنفيذ الداخلية. لا يمكن قراءة حقل password أو تعديله مباشرة من خارج الحزمة، مما يجبر الأكواد الأخرى على استخدام الأساليب (methods) التي توفرها. هذا هو نهج لغة Go لتحقيق إخفاء البيانات - وهو مبدأ أساسي للتغليف (encapsulation) في البرمجة كائنية التوجه.
التحدي
سهلدعنا نبني نظام حساب بنكي يوضح مفهوم التغليف (encapsulation) من خلال الحقول المصدرة (exported) وغير المصدرة (unexported). ستقوم بإنشاء حزمة تحمي البيانات المالية الحساسة مع عرض المعلومات العامة الآمنة.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
account.go: قم بإنشاء هيكلBankAccountفي حزمةmainيمثل حساباً بنكياً حقيقياً مع حماية مناسبة للبيانات:HolderName(مصدر - exported) - اسم صاحب الحساب، وهو آمن للعرض علناًAccountType(مصدر - exported) - نوع الحساب (مثل "Savings"، "Checking")balance(غير مصدر - unexported) - الرصيد الفعلي، والذي يجب أن يكون محمياًpin(غير مصدر - unexported) - رمز PIN الخاص بالحساب، وهي بيانات حساسة للغاية
NewBankAccountتأخذ اسم صاحب الحساب، ونوع الحساب، والرصيد الأولي، ورمز PIN، ثم تعيد مؤشراً (pointer) إلىBankAccountجديد. أضف أيضاً تابعاً (method) باسمGetPublicInfo() stringيعيد التنسيق التالي:Account: [HolderName] ([AccountType])main.go: اقرأ تفاصيل الحساب من المدخلات، وأنشئBankAccountباستخدام المنشئ، ووضح عملية التغليف. اطبع المعلومات العامة باستخدام التابع، ثم قم بالوصول مباشرة إلى كل حقل مصدر واطبعه في أسطر منفصلة. الحقول غير المصدرة (balanceوpin) محمية—يمكنك فقط تعيينها من خلال المنشئ.
سيتم توفير المدخلات التالية:
- السطر 1: اسم صاحب الحساب
- السطر 2: نوع الحساب
- السطر 3: الرصيد الأولي (كعدد عشري float)
- السطر 4: رمز PIN
على سبيل المثال، إذا كانت المدخلات Alice Johnson، Savings، 5000.50، و 1234، يجب أن يكون مخرجك:
Account: Alice Johnson (Savings)
Holder: Alice Johnson
Type: Savingsلاحظ أن الرصيد ورمز PIN لا يظهران أبداً في المخرجات—فهما مخفيان بأمان داخل الهيكل، ولا يمكن الوصول إليهما إلا داخل الحزمة. هذا هو التغليف في أبهى صوره: البيانات الحساسة محمية بينما تظل المعلومات العامة متاحة.
ورقة مرجعية
في لغة Go، يتم التحكم في التغليف (encapsulation) من خلال حالة الأحرف لأسماء الحقول في الهياكل (structs):
- الحقول التي تبدأ بـ أحرف كبيرة (Uppercase) تكون مُصدرة (exported) (يمكن الوصول إليها من أي حزمة)
- الحقول التي تبدأ بـ أحرف صغيرة (Lowercase) تكون غير مُصدرة (unexported) (يمكن الوصول إليها فقط داخل نفس الحزمة)
type User struct {
Name string // مُصدرة - يمكن الوصول إليها من أي مكان
Email string // مُصدرة - يمكن الوصول إليها من أي مكان
password string // غير مُصدرة - يمكن الوصول إليها فقط في نفس الحزمة
age int // غير مُصدرة - يمكن الوصول إليها فقط في نفس الحزمة
}
عند استخدام هيكل (struct) من حزمة أخرى، يمكن الوصول فقط إلى الحقول المُصدرة:
import "user"
func main() {
u := user.User{
Name: "Alice", // تعمل
Email: "a@mail.com", // تعمل
// password: "secret", // خطأ في التجميع: حقل غير معروف
}
fmt.Println(u.Name) // تعمل
// fmt.Println(u.password) // خطأ في التجميع: لا يمكن الإشارة إلى حقل غير مُصدر
}
تحمي هذه الآلية البيانات الحساسة وتفاصيل التنفيذ الداخلية، مما يجبر الكود الخارجي على استخدام الأساليب (methods) المتوفرة للوصول.
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// قراءة اسم صاحب الحساب
holderName, _ := reader.ReadString('\n')
holderName = strings.TrimSpace(holderName)
// قراءة نوع الحساب
accountType, _ := reader.ReadString('\n')
accountType = strings.TrimSpace(accountType)
// قراءة الرصيد الأولي
balanceStr, _ := reader.ReadString('\n')
balanceStr = strings.TrimSpace(balanceStr)
balance, _ := strconv.ParseFloat(balanceStr, 64)
// قراءة الـ PIN
pinStr, _ := reader.ReadString('\n')
pin := strings.TrimSpace(pinStr)
// TODO: إنشاء BankAccount جديد باستخدام الـ constructor
// account := NewBankAccount(...)
// TODO: طباعة المعلومات العامة باستخدام طريقة GetPublicInfo()
// TODO: طباعة اسم صاحب الحساب من خلال الوصول مباشرة إلى الحقل المصدر (exported field)
// fmt.Println("Holder:", ...)
// TODO: طباعة نوع الحساب من خلال الوصول مباشرة إلى الحقل المصدر (exported field)
// fmt.Println("Type:", ...)
// Note: الحقلان balance و pin غير مصدرين (unexported) - لا يمكن الوصول إليهما مباشرة هنا
// لقد تم تغليفهما (encapsulated) بأمان داخل الـ struct
_ = balance // قم بإزالة هذا السطر عند استخدام balance
_ = pin // قم بإزالة هذا السطر عند استخدام pin
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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 Pool