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

جو: متى يجب عليك استخدام الأنماط العامة؟ متى لا يجب عليك ذلك؟

2025/10/04 23:00

مقدمة

هذه هي نسخة المدونة من محادثاتي في Google Open Source Live:

https://youtu.be/nr8EpUO9jhw?si=jlWTapr6NM6isLgt&embedable=true

و GopherCon 2021:

https://youtu.be/Pae9EeCdy8?si=M-87Eisb2nU1qmJ&embedable=true

\ يضيف إصدار Go 1.18 ميزة لغة جديدة رئيسية: دعم البرمجة العامة. في هذا المقال، لن أصف ما هي البرمجة العامة ولا كيفية استخدامها. يتعلق هذا المقال بمتى يجب استخدام البرمجة العامة في كود Go، ومتى لا يجب استخدامها.

\ للتوضيح، سأقدم إرشادات عامة، وليست قواعد صارمة وثابتة. استخدم حكمك الخاص. ولكن إذا لم تكن متأكدًا، فأنا أوصي باستخدام الإرشادات المناقشة هنا.

اكتب الكود

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

متى تكون معلمات النوع مفيدة؟

بعد قول ذلك، دعنا ننظر في الحالات التي يمكن أن تكون فيها معلمات النوع مفيدة.

عند استخدام أنواع الحاويات المحددة باللغة

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

\ على سبيل المثال، هنا دالة تعيد شريحة من جميع المفاتيح في خريطة من أي نوع:

// MapKeys returns a slice of all the keys in m. // The keys are not returned in any particular order. func MapKeys[Key comparable, Val any](m map[Key]Val) []Key {     s := make([]Key, 0, len(m))     for k := range m {         s = append(s, k)     }     return s } 

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

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

هياكل البيانات ذات الغرض العام

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

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

\ على سبيل المثال، هذا جزء مما قد يبدو عليه هيكل بيانات شجرة ثنائية باستخدام معلمات النوع:

// Tree is a binary tree. type Tree[T any] struct {     cmp  func(T, T) int     root *node[T] }  // A node in a Tree. type node[T any] struct {     left, right  *node[T]     val          T }  // find returns a pointer to the node containing val, // or, if val is not present, a pointer to where it // would be placed if added. func (bt *Tree[T]) find(val T) **node[T] {     pl := &bt.root     for *pl != nil {         switch cmp := bt.cmp(val, (*pl).val); {         case cmp < 0:             pl = &(*pl).left         case cmp > 0:             pl = &(*pl).right         default:             return pl         }     }     return pl }  // Insert inserts val into bt if not already there, // and reports whether it was inserted. func (bt *Tree[T]) Insert(val T) bool {     pl := bt.find(val)     if *pl != nil {         return false     }     *pl = &node[T]{val: val}     return true } 

\ كل عقدة في الشجرة تحتوي على قيمة من معلمة النوع T. عندما يتم تهيئة الشجرة بوسيطة نوع معينة، سيتم تخزين قيم ذلك النوع مباشرة في العقد. لن يتم تخزينها كأنواع واجهة.

\ هذا استخدام معقول لمعلمات النوع لأن هيكل بيانات Tree، بما في ذلك الكود في الطرق، مستقل إلى حد كبير عن نوع العنصر T.

\ يحتاج هيكل بيانات Tree إلى معرفة كيفية مقارنة قيم نوع العنصر T؛ يستخدم دالة مقارنة ممررة لذلك. يمكنك رؤية هذا في السطر الرابع من طريقة find، في الاستدعاء إلى bt.cmp. بخلاف ذلك، فإن معلمة النوع لا تهم على الإطلاق.

بالنسبة لمعلمات النوع، فضل الدوال على الطرق

يوضح مثال Tree إرشادًا عامًا آخر: عندما تحتاج إلى شيء مثل دالة مقارنة، فضل الدالة على الطريقة.

\ كان يمكننا تعريف نوع Tree بحيث يكون نوع العنصر مطلوبًا أن يكون له طريقة Compare أو Less. سيتم ذلك عن طريق كتابة قيد يتطلب الطريقة، مما يعني أن أي وسيطة نوع تستخدم لتهيئة نوع Tree ستحتاج إلى أن يكون لها تلك الطريقة.

\ ستكون النتيجة أن أي شخص يريد استخدام Tree مع نوع بيانات بسيط مثل int سيضطر إلى تعريف نوع عدد صحيح خاص به وكتابة طريقة المقارنة الخاصة به. إذا قمنا بتعريف Tree لأخذ دالة مقارنة، كما في الكود الموضح أعلاه، فمن السهل تمرير الدالة المطلوبة. من السهل كتابة دالة المقارنة تلك كما هو الحال في كتابة طريقة.

\ إذا حدث أن نوع عنصر Tree لديه بالفعل طريقة Compare، فيمكننا ببساطة استخدام تعبير طريقة مثل ElementType.Compare كدالة مقارنة.

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

تنفيذ طريقة مشتركة

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

\ على سبيل المثال، ضع في اعتبارك sort.Interface في المكتبة القياسية. يتطلب أن ينفذ النوع ثلاث طرق: Len، وSwap، وLess.

\ هنا مثال على نوع عام SliceFn الذي ينفذ sort.Interface لأي نوع شريحة:

// SliceFn implements sort.Interface for a slice of T. type SliceFn[T any] struct {     s    []T     less func(T, T) bool }  func (s SliceFn[T]) Len() int {     return len(s.s) } func (s SliceFn[T]) Swap(i, j int) {     s.s[i], s.s[j] = s.s[j], s.s[i] } func (s SliceFn[T]) Less(i, j int) bool {     return s.less(s.s[i], s.s[j]) } 

\ لأي نوع شريحة، طرق Len وSwap متطابقة تمامًا. تتطلب طريقة Less مقارنة، وهي جزء Fn من الاسم SliceFn. كما هو الحال مع مثال Tree السابق، سنمرر دالة عند إنشاء SliceFn.

\ هنا كيفية استخدام SliceFn لفرز أي شريحة باستخدام دالة مقارنة:

// SortFn sorts s in place using a comparison function. func SortFn[T any](s []T, less func(T, T) bool) {     sort.Sort(SliceFn[T]{s, less}) } 

\ هذا مشابه لدالة المكتبة القياسية sort.Slice، ولكن دالة المقارنة مكتوبة باستخدام القيم بدلاً من فهارس الشريحة.

\ استخدام معلمات النوع لهذا النوع من الكود منا

إخلاء مسؤولية: المقالات المُعاد نشرها على هذا الموقع مستقاة من منصات عامة، وهي مُقدمة لأغراض إعلامية فقط. لا تُظهِر بالضرورة آراء MEXC. جميع الحقوق محفوظة لمؤلفيها الأصليين. إذا كنت تعتقد أن أي محتوى ينتهك حقوق جهات خارجية، يُرجى التواصل عبر البريد الإلكتروني service@support.mexc.com لإزالته. لا تقدم MEXC أي ضمانات بشأن دقة المحتوى أو اكتماله أو حداثته، وليست مسؤولة عن أي إجراءات تُتخذ بناءً على المعلومات المُقدمة. لا يُمثل المحتوى نصيحة مالية أو قانونية أو مهنية أخرى، ولا يُعتبر توصية أو تأييدًا من MEXC.
مشاركة الرؤى

قد يعجبك أيضاً