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

قواعد استيفاء الـ 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 من قيمة (فقد لا تكون القيمة قابلة لأخذ عنوانها). يساعدك وضع هذه القاعدة في الاعتبار على تجنّب أخطاء المترجم المربكة عند التعامل مع الواجهات.

challenge icon

التحدي

سهل

لنَبْنِ نظام إعدادات يوضّح كيف يؤثر مستقبِلا 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: اقرأ تفاصيل الإعدادات من الإدخال ووضّح قواعد تحقيق الواجهة:
    1. أنشئ ReadOnlyConfig ومرّر pointer إلى ProcessConfig (مطلوب لأن SetValue has مستقبِل pointer)
    2. أنشئ 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
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Go عبر الإنترنت