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

التغليف على مستوى الحزمة

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 48 من 107.

بينما غطى الدرس السابق الرؤية على مستوى الحقل، فإن تغليف Go يعمل في الواقع على مستوى الحزمة. كل شيء داخل نفس الحزمة - بغض النظر عن الملف الموجود فيه - يمكنه الوصول إلى الحقول والوظائف غير المصدرة.

ضع في اعتبارك حزمة تحتوي على ملفات متعددة:

// 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 غير المصدر. هذا مقصود—فهو يسمح لك بتقسيم منطق الحزمة عبر ملفات متعددة مع الحفاظ على إمكانية الوصول الداخلية.

ومع ذلك، لا يمكن للكود الموجود خارج الحزمة الوصول إلى الأعضاء غير المصدرة (unexported):

// main.go
package main

import "account"

func main() {
    acc := &account.Account{}
    acc.Deposit(100)        // يعمل - دالة (method) مصدّرة
    // acc.balance = 50     // خطأ - حقل غير مصدّر
    // account.ResetAccount // سيعمل إذا كانت ResetAccount مصدّرة
}

تُعد حدود مستوى الحزمة هذه آلية التغليف الأساسية في لغة Go. أنت تقوم بتصميم الحزم كوحدات متماسكة حيث يتعاون الكود الداخلي بحرية، بينما يتفاعل الكود الخارجي فقط من خلال واجهة برمجة التطبيقات (API) المصدرة. فكر في كل حزمة كنموذج قائم بذاته بواجهة عامة واضحة.

challenge icon

التحدي

سهل

لنقم ببناء نظام لإدارة المخزون يوضح كيف يمكن لملفات متعددة داخل نفس الحزمة (package) التعاون من خلال الوصول إلى الحقول غير المصدرة (unexported fields) مباشرة، مع الحفاظ على تلك البيانات مخفية عن الكود الخارجي.

ستقوم بإنشاء ثلاثة ملفات تعمل معاً في الحزمة main:

  • product.go: قم بتعريف هيكل Product مع حقل مصدر Name وحقل غير مصدر quantity (من نوع int). أضف تابعاً (method) مصدراً باسم Info() string يعيد التنسيق التالي: [Name]: [quantity] in stock
  • inventory.go: قم بإنشاء دوال مساعدة تتعامل مع المنتجات من خلال الوصول المباشر إلى الحقل غير المصدر quantity (وهو أمر ممكن لأنها في نفس الحزمة). قم بتنفيذ ما يلي:
    • Restock(p *Product, amount int) - يضيف الكمية إلى مخزون المنتج.
    • Sell(p *Product, amount int) bool - يطرح الكمية من المخزون إذا توفر مخزون كافٍ، ويعيد true في حال النجاح أو false إذا لم يكن المخزون كافياً.
    • NewProduct(name string, initialQty int) *Product - منشئ (constructor) يقوم بإنشاء منتج بالاسم المعطى والكمية الأولية.
  • main.go: اقرأ تفاصيل المنتج من المدخلات، وأنشئ منتجاً باستخدام المنشئ، وقم بإجراء عمليات المخزون، واعرض النتائج. بعد كل عملية، اطبع معلومات المنتج لإظهار الحالة المحدثة.

سيتم توفير المدخلات التالية:

  • السطر 1: اسم المنتج
  • السطر 2: الكمية الأولية (عدد صحيح)
  • السطر 3: كمية إعادة التعبئة (عدد صحيح)
  • السطر 4: كمية البيع (عدد صحيح)

بعد إنشاء المنتج، اطبع معلوماته الأولية. ثم قم بإعادة التعبئة واطبع المعلومات مرة أخرى. أخيراً، حاول البيع واطبع إما المعلومات المحدثة (في حال النجاح) أو Sale failed: insufficient stock (إذا لم تكن الكمية كافية).

على سبيل المثال، إذا كانت المدخلات 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. هذا هو التغليف (encapsulation) على مستوى الحزمة في أبهى صوره — حيث يحدث التعاون الداخلي بحرية بينما تظل البيانات محمية من الحزم الخارجية.

ورقة مرجعية

يعمل التغليف (encapsulation) في لغة Go على مستوى الحزمة، وليس على مستوى الملف. يمكن لجميع الملفات الموجودة داخل نفس الحزمة الوصول إلى الحقول والوظائف غير المصدرة (unexported)، بغض النظر عن الملف الذي تم تعريفها فيه.

الوصول على مستوى الحزمة

يمكن الوصول إلى الأعضاء غير المصدرة عبر جميع الملفات في نفس الحزمة:

// 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  // يعمل - نفس الحزمة
}

قيود الحزم الخارجية

لا يمكن للكود الموجود خارج الحزمة الوصول إلى الأعضاء غير المصدرة:

// main.go
package main

import "account"

func main() {
    acc := &account.Account{}
    acc.Deposit(100)        // يعمل - دالة (method) مصدرة
    // acc.balance = 50     // خطأ - حقل غير مصدر
}

يسمح هذا التصميم بتقسيم منطق الحزمة عبر ملفات متعددة مع الحفاظ على الوصول الداخلي، مما يؤدي إلى إنشاء وحدات متماسكة ذات واجهات عامة واضحة.

جرّب بنفسك

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: محاولة البيع وطباعة النتيجة
	// إذا نجحت عملية البيع، اطبع المعلومات المحدثة
	// إذا فشلت عملية البيع، اطبع "Sale failed: insufficient stock"

	fmt.Println() // استبدله بالمخرجات الفعلية
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming