قواعد استيفاء الـ Interface
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 43 من 107.
بينما تستخدم Go أسلوب الكتابة وفق السلوك لتحقيق الامتثال الضمني للواجهة، توجد قواعد محددة تحدد ما إذا كان النوع يحقق الواجهة فعليًا. يساعدك فهم هذه القواعد على تجنب الأخطاء الخفية.
تتعلق أهم قاعدة بـ pointer receivers. إذا تم تعريف method باستخدام pointer receiver، فإن pointer إلى ذلك النوع فقط هو الذي يحقق interface، وليس القيمة نفسها:
type Saver interface {
Save() string
}
type Document struct{ Name string }
func (d *Document) Save() string { // مستقبل المؤشر
return "Saved: " + d.Name
}
func Process(s Saver) {
fmt.Println(s.Save())
}
لأن Save() لديه مستقبل pointer، فإن *Document فقط يحقق Saver:
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // يعمل - المؤشر يلبي الواجهة
// Process(doc) // خطأ في الترجمة - القيمة لا تلبي الواجهة
}
ومع ذلك، فإن العكس أكثر مرونة. إذا كان لدى إحدى الطرق مستقبل قيمة، فإن both نوع القيمة ونوع pointer يستوفيان متطلبات interface. تقوم Go تلقائيًا بإلغاء الإشارة إلى المؤشرات عند استدعاء طرق مستقبل القيمة:
func (d Document) Info() string { // مستقبل بالقيمة
return d.Name
}
// كلا من Document و *Document يلبيان واجهة تتطلب Info()
يوجد عدم التماثل هذا لأن Go يمكنه دائمًا الحصول على قيمة من pointer (من خلال إلغاء الإشارة)، لكنه لا يستطيع دائمًا الحصول على pointer من قيمة (فقد لا تكون القيمة قابلة لأخذ عنوانها). يساعدك وضع هذه القاعدة في الاعتبار على تجنّب أخطاء المترجم المربكة عند التعامل مع الواجهات.
التحدي
سهللنَبْنِ نظام إعدادات يوضّح كيف يؤثر مستقبِلا pointer وvalue في تحقيق الواجهة. ستنشئ أنواعًا يحدد فيها اختيار المستقبِل ما إذا كان يمكن استخدام القيم أو المؤشرات أو كليهما مع واجهة.
ستنظّم التعليمات البرمجية في ثلاثة ملفات:
config.go: Define واجهةConfigurableتتطلب طريقتين:GetValue() stringوSetValue(string). ثم أنشئ نوعي إعدادات:ReadOnlyConfigمع حقلValue: استخدم مستقبِل قيمة لـGetValue()(يعيد Value) ومستقبِل pointer لـSetValue()(يحدّث Value)Settingمع حقلData: استخدم مستقبِلات قيمة للطريقتين (تعيد GetValue قيمة Data، بينما تطبع SetValue فقط "Cannot modify" من دون تغيير أي شيء)
processor.go: أنشئ function باسمProcessConfigتقبلConfigurableوسلسلة قيمة جديدة. ينبغي أن تطبع القيمة الحالية باستخدامGetValue()، ثم تستدعيSetValue()بالقيمة الجديدة، وبعد ذلك تطبع القيمة مرة أخرى لإظهار أي changes.main.go: اقرأ تفاصيل الإعدادات من الإدخال ووضّح قواعد تحقيق الواجهة:- أنشئ
ReadOnlyConfigومرّر pointer إلىProcessConfig(مطلوب لأنSetValuehas مستقبِل pointer) - أنشئ
Settingومرّر القيمة directly إلىProcessConfig(يعمل لأن both methods لديهما مستقبِلات قيمة)
- أنشئ
سيتم توفير inputs التالية:
- line 1: القيمة الأولية لـ ReadOnlyConfig
- line 2: القيمة الجديدة المطلوب تعيينها لـ ReadOnlyConfig
- line 3: القيمة الأولية لـ Setting
- line 4: القيمة الجديدة المطلوب تجربتها لـ Setting
ينبغي أن تطبع function ProcessConfig بالتنسيق التالي:
Current: [value]
Current: [value after SetValue]على سبيل المثال، عند إعطاء debug وproduction وlocalhost وremote، ينبغي أن يكون الناتج:
Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhostلاحظ كيف أن ReadOnlyConfig يغيّر قيمته فعليًا (لأننا مرّرنا pointer)، بينما يبقى Setting دون تغيير (إذ لا يستطيع SetValue، مع مستقبِل قيمة، تعديل الأصل). تتمثل الفكرة الأساسية في أن قيم ReadOnlyConfig وحدها لن تحقق Configurable؛ بل المؤشرات فقط هي التي تحققها، بينما تعمل قيم Setting مباشرةً لأن جميع methods الخاصة بها تستخدم مستقبِلات قيمة.
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// اقرأ المدخلات
scanner.Scan()
rocInitial := scanner.Text()
scanner.Scan()
rocNew := scanner.Text()
scanner.Scan()
settingInitial := scanner.Text()
scanner.Scan()
settingNew := scanner.Text()
// TODO: أنشئ ReadOnlyConfig بقيمة rocInitial
// TODO: مرّر POINTER إلى ProcessConfig (مطلوب لأن SetValue لديه pointer receiver)
// TODO: أنشئ Setting بقيمة settingInitial
// TODO: مرّر VALUE مباشرة إلى ProcessConfig (يعمل لأن كلا الطريقتين لديهما value receivers)
// استخدم هذه المتغيرات لتجنب أخطاء المتغيرات غير المستخدمة
_ = rocInitial
_ = rocNew
_ = settingInitial
_ = settingNew
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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تدرّب بنفسك: مترجم Go عبر الإنترنت