الأنواع العامة في لغة Go: حالات الاستخدام والأنماط

الكود القابل لإعادة الاستخدام ذو النوع الآمن باستخدام generics في Go

Page content

الأنواع العامة في Go تمثل واحدة من أهم ميزات اللغة المضافة منذ Go 1.0. تم تقديمها في Go 1.18، تسمح الأنواع العامة لك لكتابة كود آمن من حيث النوع وقابل لإعادة الاستخدام يعمل مع عدة أنواع دون التضحية بالأداء أو وضوح الكود.

يستعرض هذا المقال استخدامات عملية، أنماط شائعة، و أفضل الممارسات لاستغلال الأنواع العامة في برامجك Go. إذا كنت جديدًا في Go أو تحتاج إلى تذكير حول الأساسيات، فراجع قائمة مصطلحات Go الخاصة بنا للحصول على بنى اللغة الأساسية والقواعد النحوية.

مُبرمج وحيد

فهم الأنواع العامة في Go

تتيح الأنواع العامة في Go كتابة الدوال والأنواع التي تُحدد بواسطة معلمات نوع. هذا يلغي الحاجة إلى تكرار الكود عندما تحتاج نفس المنطق للعمل مع أنواع مختلفة، مع الحفاظ على أمان النوع في وقت التجميع.

التركيب الأساسي

يستخدم التركيب الخاص بالأنواع العامة الأقواس المربعة لتحديد معلمات النوع:

// دالة عامة
func Max[T comparable](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// الاستخدام
maxInt := Max(10, 20)
maxString := Max("apple", "banana")

قيود النوع

تُحدد قيود النوع ما أنواع يمكن استخدامها مع الكود العام:

  • any: أي نوع (مكافئ لـ interface{} )
  • comparable: الأنواع التي تدعم المشغلين == و !=
  • القيود الخاصة بالواجهات: تعريف متطلباتك الخاصة
// استخدام قيد مخصص
type Numeric interface {
    int | int8 | int16 | int32 | int64 | 
    uint | uint8 | uint16 | uint32 | uint64 | 
    float32 | float64
}

func Sum[T Numeric](numbers []T) T {
    var sum T
    for _, n := range numbers {
        sum += n
    }
    return sum
}

استخدامات شائعة

1. الهياكل العامة للبيانات

أحد أكثر الاستخدامات مقنعة للأنواع العامة هو إنشاء هياكل بيانات قابلة لإعادة الاستخدام:

// مكدس عام
type Stack[T any] struct {
    items []T
}

func NewStack[T any]() *Stack[T] {
    return &Stack[T]{items: make([]T, 0)}
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

// الاستخدام
intStack := NewStack[int]()
intStack.Push(42)
intStack.Push(100)

stringStack := NewStack[string]()
stringStack.Push("hello")

2. أدوات المصفوفات

تُسهل الأنواع العامة كتابة وظائف معالجة مصفوفات قابلة لإعادة الاستخدام:

// دالة Mapping
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// دالة Filter
func Filter[T any](slice []T, fn func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

// دالة Reduce
func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

// الاستخدام
numbers := []int{1, 2, 3, 4, 5}
doubled := Map(numbers, func(n int) int { return n * 2 })
evens := Filter(numbers, func(n int) bool { return n%2 == 0 })
sum := Reduce(numbers, 0, func(acc, n int) int { return acc + n })

3. أدوات المخططات العامة

تصبح العمل مع المخططات أكثر أمانًا من حيث النوع مع الأنواع العامة:

// الحصول على مفاتيح المخطط كمصفوفة
func Keys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

// الحصول على قيم المخطط كمصفوفة
func Values[K comparable, V any](m map[K]V) []V {
    values := make([]V, 0, len(m))
    for _, v := range m {
        values = append(values, v)
    }
    return values
}

// الحصول على قيمة من المخطط مع قيمة افتراضية
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V {
    if v, ok := m[key]; ok {
        return v
    }
    return defaultValue
}

4. نمط الخيار العام

يصبح نمط الخيار أكثر أناقة مع الأنواع العامة:

type Option[T any] struct {
    value *T
}

func Some[T any](value T) Option[T] {
    return Option[T]{value: &value}
}

func None[T any]() Option[T] {
    return Option[T]{value: nil}
}

func (o Option[T]) IsSome() bool {
    return o.value != nil
}

func (o Option[T]) IsNone() bool {
    return o.value == nil
}

func (o Option[T]) Unwrap() T {
    if o.value == nil {
        panic("تم محاولة فك تغليف قيمة None")
    }
    return *o.value
}

func (o Option[T]) UnwrapOr(defaultValue T) T {
    if o.value == nil {
        return defaultValue
    }
    return *o.value
}

الأنماط المتقدمة

تكوين القيود

يمكنك تكوين القيود لإنشاء متطلبات أكثر تحديدًا:

type Addable interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64 | string
}

type Multiplicable interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64
}

type Numeric interface {
    Addable
    Multiplicable
}

func Multiply[T Multiplicable](a, b T) T {
    return a * b
}

الواجهات العامة

يمكن أن تكون الواجهات أيضًا عامة، مما يتيح تجريدات قوية:

type Repository[T any, ID comparable] interface {
    FindByID(id ID) (T, error)
    Save(entity T) error
    Delete(id ID) error
    FindAll() ([]T, error)
}

// التنفيذ
type InMemoryRepository[T any, ID comparable] struct {
    data map[ID]T
}

func NewInMemoryRepository[T any, ID comparable]() *InMemoryRepository[T, ID] {
    return &InMemoryRepository[T, ID]{
        data: make(map[ID]T),
    }
}

func (r *InMemoryRepository[T, ID]) FindByID(id ID) (T, error) {
    if entity, ok := r.data[id]; ok {
        return entity, nil
    }
    var zero T
    return zero, fmt.Errorf("الكيان غير موجود")
}

استنتاج النوع

غالبًا ما يسمح استنتاج النوع في Go بحذف معلمات النوع الصريحة:

// استنتاج النوع في العمل
numbers := []int{1, 2, 3, 4, 5}

// لا حاجة لتحديد [int] - Go يستنتجها
doubled := Map(numbers, func(n int) int { return n * 2 })

// تحديد معلمات النوع عند الحاجة
result := Map[int, string](numbers, strconv.Itoa)

أفضل الممارسات

1. ابدأ ببساطة

لا تستخدم الأنواع العامة بشكل مفرط. إذا كانت واجهة بسيطة أو نوعًا محددًا كافيين، فاخترهما لتحسين قابلية القراءة:

// تفضيل هذا في الحالات البسيطة
func Process(items []Processor) {
    for _, item := range items {
        item.Process()
    }
}

// عام جدًا - تجنب
func Process[T Processor](items []T) {
    for _, item := range items {
        item.Process()
    }
}

2. استخدام أسماء واضحة للقيود

اسم قيودك بوضوح لنقل النية:

// جيد
type Sortable interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64 | string
}

// أقل وضوحًا
type T interface {
    int | string
}

3. وثيق القيود المعقدة

عندما تصبح القيود معقدة، أضف وثائق:

// Numeric يمثل الأنواع التي تدعم العمليات الحسابية.
// يشمل ذلك جميع الأنواع الصحيحة والعددية.
type Numeric interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64
}

4. تجنب الأداء

تُترجم الأنواع العامة إلى أنواع محددة في وقت التجميع، لذا لا توجد مساحة للتشغيل. ومع ذلك، كن حذرًا من حجم الكود إذا قمت بإنشاء العديد من مزيج الأنواع:

// كل مزيج ينشئ كودًا منفصلًا
var intStack = NewStack[int]()
var stringStack = NewStack[string]()
var floatStack = NewStack[float64]()

التطبيقات العملية

مُنشئات استعلامات قاعدة البيانات

تُستخدم الأنواع العامة بشكل خاص عند بناء مُنشئات استعلامات قاعدة البيانات أو العمل مع ORMs. عند العمل مع قواعد البيانات في Go، قد تجد مقارنتنا بين ORMs لـ PostgreSQL في Go مفيدة لفهم كيفية تحسين أمان النوع في عمليات قاعدة البيانات.

type QueryBuilder[T any] struct {
    table string
    where []string
}

func (qb *QueryBuilder[T]) Where(condition string) *QueryBuilder[T] {
    qb.where = append(qb.where, condition)
    return qb
}

func (qb *QueryBuilder[T]) Find() ([]T, error) {
    // التنفيذ
    return nil, nil
}

إدارة التكوين

عند بناء تطبيقات سطر الأوامر أو أنظمة التكوين، يمكن للأنواع العامة مساعدة إنشاء مُحمِّلين تكوين آمنين من حيث النوع. إذا كنت تبني أدوات سطر الأوامر، فإن دليلنا حول بناء تطبيقات سطر الأوامر مع Cobra & Viper يوضح كيفية استخدام الأنواع العامة لتحسين إدارة التكوين.

type ConfigLoader[T any] struct {
    path string
}

func (cl *ConfigLoader[T]) Load() (T, error) {
    var config T
    // تحميل وفك تشفير التكوين
    return config, nil
}

المكتبات العامة

تبرز الأنواع العامة عندما تُنشئ مكتبات مساعدة تعمل مع أنواع مختلفة. على سبيل المثال، عند إنشاء تقارير أو العمل مع تنسيقات بيانات مختلفة، يمكن للأنواع العامة توفير أمان النوع. مقالتنا حول إنشاء تقارير PDF في Go توضح كيفية تطبيق الأنواع العامة على أدوات إنشاء التقارير.

الكود الحساس للأداء

في التطبيقات الحساسة للأداء مثل وظائف Serverless، يمكن للأنواع العامة مساعدة الحفاظ على أمان النوع دون مساحة للتشغيل. عند النظر في خيارات اللغة لتطبيقات Serverless، فإن فهم خصائص الأداء أمر حيوي. تحليلنا حول أداء AWS Lambda عبر JavaScript، Python، و Golang يوضح كيف يمكن أن يكون أداء Go، مع الأنواع العامة، مفيدًا.

الأخطاء الشائعة

1. تقييد الأنواع بشكل مفرط

تجنب جعل القيود مفرطة عندما لا تحتاج إلى ذلك:

// مفرط في التقييد
func Process[T int | string](items []T) { }

// أفضل - أكثر مرونة
func Process[T comparable](items []T) { }

2. تجاهل استنتاج النوع

دع Go يستنتج الأنواع عندما يكون من الممكن:

// غير ضروري صراحة
result := Max[int](10, 20)

// أفضل - دع Go يستنتج
result := Max(10, 20)

3. نسيان القيم الافتراضية

تذكّر أن الأنواع العامة لها قيم افتراضية:

func Get[T any](slice []T, index int) (T, bool) {
    if index < 0 || index >= len(slice) {
        var zero T  // مهم: عدّد القيمة الافتراضية
        return zero, false
    }
    return slice[index], true
}

الخاتمة

تُوفر الأنواع العامة في Go طريقة قوية لكتابة كود آمن من حيث النوع وقابل لإعادة الاستخدام دون التضحية بالأداء أو القابلية للقراءة. من خلال فهم التركيب، القيود، والأنماط الشائعة، يمكنك الاستفادة من الأنواع العامة لتقليل تكرار الكود وتحسين أمان النوع في تطبيقاتك Go.

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

مع استمرار تطور Go، تصبح الأنواع العامة عنصرًا أساسيًا لبناء تطبيقات حديثة آمنة من حيث النوع. سواء كنت تبني هياكل بيانات، مكتبات مساعدة، أو تجريدات معقدة، يمكن للأنواع العامة مساعدتك في كتابة كود أوضح وأكثر قابلية للصيانة.

الروابط المفيدة والمقالات المرتبطة