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

الـ 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) على هذه الأنواع العامة.

challenge icon

التحدي

سهل

لنقم ببناء نظام مخزون عام (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 للتكرار
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming