الـ Structs العامة
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 70 من 107.
تماماً مثل الدوال، يمكن أن تحتوي الهياكل (structs) أيضاً على معاملات أنواع. يسمح لك الهيكل العام (generic struct) بإنشاء هياكل بيانات تعمل مع أي نوع مع الحفاظ على سلامة الأنواع. هذا مثالي لبناء حاويات قابلة لإعادة الاستخدام مثل الأكداس (stacks)، أو الطوابير (queues)، أو أزواج المفتاح والقيمة (key-value pairs).
لتعريف هيكل (struct) عام، ضع معامل النوع بعد اسم الهيكل:
type Box[T any] struct {
Value T
}
func main() {
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}
fmt.Println(intBox.Value) // 42
fmt.Println(strBox.Value) // hello
}على عكس الدوال العامة (generic functions)، يجب عليك تحديد النوع بشكل صريح عند إنشاء مثيل لهيكل عام (generic struct). لا تستطيع لغة Go استنتاج النوع من السياق وحده.
يمكن أن تحتوي الهياكل العامة (Generic structs) على معلمات أنواع متعددة، مما يجعلها مثالية لهياكل مثل الأزواج (pairs) أو الخرائط (maps):
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value) // age: 25يمكنك أيضاً تطبيق القيود لضمان دعم الأنواع المخزنة لعمليات محددة:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}تشكل البنى العامة (Generic structs) الأساس لبناء هياكل بيانات آمنة من حيث النوع (type-safe) وقابلة لإعادة الاستخدام في لغة Go. في الدرس القادم، ستتعلم كيفية تعريف الدوال (methods) على هذه الأنواع العامة.
التحدي
سهللنقم ببناء نظام مخزون عام (generic) يمكنه تتبع أنواع مختلفة من العناصر! ستقوم بإنشاء هياكل حاويات قابلة لإعادة الاستخدام تعمل مع أي نوع مع الحفاظ على سلامة الأنواع (type safety) الكاملة.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
inventory.go: قم بتعريف هياكل الحاويات العامة الخاصة بك.أنشئ هيكلاً عاماً
Item[T any]يحتوي على حقلين:Name(string) وData(من النوع T). يمثل هذا أي عنصر مع بيانات مرتبطة به من نوع مرن.أنشئ هيكلاً عاماً
Container[T any]يحتوي على حقل واحدItemsيحمل شريحة (slice) منItem[T]. يمكن لهذه الحاوية تخزين عناصر متعددة من نفس نوع البيانات.أنشئ هيكلاً عاماً مقيداً
PricedItem[T int | float64]يحتوي على ثلاثة حقول:Name(string) وQuantity(int) وPrice(من النوع T). يضمن القيد (constraint) أن تكون الأسعار دائماً عددية.قم بتنفيذ دالة
NewContainer[T any]() *Container[T]تقوم بإنشاء وإرجاع مؤشر (pointer) إلى حاوية فارغة.قم بتنفيذ تابع (method) باسم
Addعلى*Container[T]يأخذ اسماً (string) وبيانات (T)، ويقوم بإنشاء Item، ويضيفه إلى شريحة Items الخاصة بالحاوية.قم بتنفيذ تابع
CountعلىContainer[T]يرجع عدد العناصر في الحاوية.main.go: استعرض هياكلك العامة مع أنواع مختلفة.اقرأ نوع العنصر (
string، أوint، أوpriced)، ثم اقرأ عدداً متبوعاً بتفاصيل العناصر.لنوع
string: اقرأ أزواجاً من الاسم وبيانات نصية. أنشئContainer[string]، وأضف جميع العناصر، ثم اطبع كل عنصر بتنسيق[Name]: [Data]متبوعاً بالعدد الإجمالي.لنوع
int: اقرأ أزواجاً من الاسم وبيانات صحيحة. أنشئContainer[int]، وأضف جميع العناصر، ثم اطبع كل عنصر بتنسيق[Name]: [Data]متبوعاً بالعدد الإجمالي.لنوع
priced: اقرأ ثلاثيات من الاسم، والكمية، والسعر (كـ float). أنشئ مثيلاتPricedItem[float64]مباشرة واطبع كل منها بتنسيق[Name] x[Quantity] @ [Price]مع إظهار السعر بمنزلة عشرية واحدة.اطبع سطر العدد كالتالي:
Total items: [count]
سيتم توفير المدخلات التالية:
- السطر 1: نوع العنصر (
string، أوint، أوpriced) - السطر 2: عدد العناصر (عدد صحيح)
- الأسطر التالية: تفاصيل العناصر بناءً على النوع
على سبيل المثال، إذا كان المدخل:
string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined Paperيجب أن يكون مخرجك:
Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3وإذا كان المدخل:
int
2
Apples
50
Oranges
30يجب أن يكون مخرجك:
Apples: 50
Oranges: 30
Total items: 2وإذا كان المدخل:
priced
2
Widget
10
19.99
Gadget
5
49.50يجب أن يكون مخرجك:
Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2لاحظ كيف يعمل هيكل Container نفسه بسلاسة مع كل من النصوص والأعداد الصحيحة، بينما يستخدم PricedItem قيداً لضمان إمكانية استخدام الأنواع الرقمية فقط للأسعار. يجب عليك تحديد معامل النوع (type parameter) صراحةً عند إنشاء مثيلات من هذه الهياكل العامة.
ورقة مرجعية
تسمح لك البنية العامة (generic struct) بإنشاء هياكل بيانات تعمل مع أي نوع مع الحفاظ على سلامة النوع (type safety).
لتعريف بنية عامة، ضع معامل النوع بعد اسم البنية:
type Box[T any] struct {
Value T
}عند إنشاء مثيل لبنية عامة، يجب عليك تحديد النوع صراحةً (لا تستطيع لغة Go استنتاجه):
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}يمكن أن تحتوي البنيات العامة على معاملات أنواع متعددة:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}يمكنك تطبيق قيود لضمان أن الأنواع المخزنة تدعم عمليات محددة:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}يمكن أن تحتوي البنيات العامة على حقول تستخدم معامل النوع، بما في ذلك الشرائح (slices):
type Container[T any] struct {
Items []T
}يمكنك تعريف دوال (methods) على البنيات العامة وإنشاء دوال بناء (constructor functions) عامة:
func NewContainer[T any]() *Container[T] {
return &Container[T]{Items: []T{}}
}
func (c *Container[T]) Add(item T) {
c.Items = append(c.Items, item)
}جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// قراءة نوع العنصر
scanner.Scan()
itemType := scanner.Text()
// قراءة العدد
scanner.Scan()
count, _ := strconv.Atoi(scanner.Text())
switch itemType {
case "string":
// TODO: إنشاء Container[string] باستخدام NewContainer
// TODO: قراءة 'count' من العناصر (كل عنصر له اسم في سطر، وبيانات في السطر التالي)
// TODO: إضافة كل عنصر إلى الحاوية
// TODO: طباعة كل عنصر بتنسيق "[Name]: [Data]"
// TODO: طباعة "Total items: [count]" باستخدام التابع Count
case "int":
// TODO: إنشاء Container[int] باستخدام NewContainer
// TODO: قراءة 'count' من العناصر (كل عنصر له اسم في سطر، وبيانات صحيحة في السطر التالي)
// TODO: إضافة كل عنصر إلى الحاوية
// TODO: طباعة كل عنصر بتنسيق "[Name]: [Data]"
// TODO: طباعة "Total items: [count]" باستخدام التابع Count
case "priced":
// TODO: إنشاء slice للاحتفاظ بنماذج PricedItem[float64]
// TODO: قراءة 'count' من العناصر (كل عنصر له اسم، وكمية، وسعر في أسطر منفصلة)
// TODO: إنشاء PricedItem[float64] لكل عنصر وإضافته إلى الـ slice
// TODO: طباعة كل عنصر بتنسيق "[Name] x[Quantity] @ [Price]" مع إظهار السعر بمنزلة عشرية واحدة
// TODO: طباعة "Total items: [count]"
}
_ = scanner // استخدام scanner لقراءة المدخلات
_ = count // استخدام count للتكرار
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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