التغليف على مستوى الحزمة
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 48 من 107.
بينما تناول الدرس السابق مستوى رؤية field، فإن التغليف في Go يعمل فعليًا على مستوى الحزمة. كل ما يوجد داخل الحزمة نفسها، بغضّ النظر عن الملف الذي يوجد فيه، يمكنه الوصول إلى الحقول وfunction غير المُصدَّرة.
تأمل حزمة تحتوي على ملفات متعددة:
// account/account.go
package account
type Account struct {
balance int // غير مُصدَّر
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // يعمل - نفس الحزمة
}
ينتمي كلا الملفين إلى الحزمة account، لذا يمكن لـ helper.go الوصول مباشرةً إلى الحقل غير المُصدَّر balance. هذا مقصود، إذ يتيح لك تقسيم منطق الحزمة عبر ملفات متعددة مع الحفاظ على الوصول الداخلي.
ومع ذلك، لا يمكن للكود الموجود خارج الحزمة الوصول إلى الأعضاء غير المُصدَّرة:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // يعمل - method مُصدَّر
// acc.balance = 50 // خطأ - حقل غير مُصدَّر
// account.ResetAccount // يعمل إذا كان ResetAccount مُصدَّرًا
}
يُعدّ هذا الحدّ على مستوى الحزمة آلية التغليف الأساسية في Go. تصمّم الحزم كوحدات متماسكة، حيث يتعاون الكود الداخلي بحرية، بينما يتفاعل الكود الخارجي فقط من خلال واجهة API المُصدَّرة exported. فكّر في كل حزمة على أنها وحدة مستقلة ذات واجهة عامة واضحة.
التحدي
سهللنَبْنِ نظامًا لإدارة inventory يوضّح كيف يمكن لملفات متعددة داخل الحزمة نفسها أن تتعاون من خلال الوصول مباشرةً إلى الحقول غير المُصدَّرة، مع إبقاء تلك البيانات مخفيةً عن الشيفرة الخارجية.
ستُنشئ ثلاثة ملفات تعمل معًا في الحزمة main:
product.go: عرّف بنيةProductمع حقلNameمُصدَّر وحقلquantityغير مُصدَّر (من النوع int). أضف method مُصدَّرةInfo() stringتُعيد format:[Name]: [quantity] in stockinventory.go: أنشئ helper functions تعمل مع المنتجات من خلال الوصول مباشرةً إلى الحقل غير المُصدَّرquantity(وهو أمر ممكن لأنها في الحزمة نفسها). Implement:Restock(p *Product, amount int)- adds المقدار إلى quantity المنتجSell(p *Product, amount int) bool- يطرح المقدار من quantity إذا كان المخزون enough، ويُعيدtrueعند النجاح أوfalseإذا لم يكن المخزون enoughNewProduct(name string, initialQty int) *Product- constructor ينشئ منتجًا بالاسم والمقدار الأولي given
main.go: اقرأ تفاصيل المنتج من الإدخال، وأنشئ منتجًا باستخدام constructor، ونفّذ عمليات inventory، واعرض النتائج. بعد كل عملية، اطبع معلومات المنتج لإظهار الحالة المحدَّثة.
سيتم توفير المدخلات التالية:
- السطر 1: اسم المنتج
- السطر 2: المقدار الأولي (عدد صحيح)
- السطر 3: مقدار إعادة التخزين (عدد صحيح)
- السطر 4: مقدار البيع (عدد صحيح)
بعد إنشاء المنتج، اطبع معلوماته الأولية. ثم نفّذ إعادة التخزين واطبع المعلومات مرة أخرى. أخيرًا، حاول البيع واطبع إما المعلومات المحدَّثة (إذا نجح) أو Sale failed: insufficient stock (إذا لم تكن الكمية كافية).
على سبيل المثال، given Laptop و10 و5 و8، يجب أن يكون الناتج:
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stockوبالنسبة إلى Phone و3 و2 و10، يجب أن يكون الناتج:
Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stockالفكرة الأساسية هنا هي أن inventory.go يستطيع قراءة الحقل غير المُصدَّر quantity وتعديله مباشرةً لأنه موجود في الحزمة نفسها مثل product.go. هذا هو مبدأ التغليف على مستوى الحزمة عمليًا: يحدث التعاون الداخلي بحرية، بينما تظل البيانات محميةً من الحزم الخارجية.
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// اقرأ اسم المنتج
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
// اقرأ الكمية الأولية
initialQtyStr, _ := reader.ReadString('\n')
initialQty, _ := strconv.Atoi(strings.TrimSpace(initialQtyStr))
// اقرأ كمية إعادة التخزين
restockStr, _ := reader.ReadString('\n')
restockAmount, _ := strconv.Atoi(strings.TrimSpace(restockStr))
// اقرأ كمية البيع
sellStr, _ := reader.ReadString('\n')
sellAmount, _ := strconv.Atoi(strings.TrimSpace(sellStr))
// TODO: أنشئ منتجًا جديدًا باستخدام المُنشئ NewProduct
// TODO: اطبع معلومات المنتج الأولية
// TODO: أعد تخزين المنتج واطبع المعلومات
// TODO: حاول البيع واطبع النتيجة
// إذا نجح البيع، اطبع المعلومات المحدثة
// If sale fails, print "Sale failed: insufficient stock"
fmt.Println() // استبدل بالمخرجات الفعلية
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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تدرّب بنفسك: مترجم Go عبر الإنترنت