الواجهات
Interfaces
الواجهات — Interfaces
الواجهات هي من أجمل مفاهيم Go وأكثرها أناقة. الفكرة بسيطة لكنها عميقة: الواجهة تُعرّف سلوكاً، لا بنية. أي نوع يُنفّذ أساليب الواجهة يُحقق تلك الواجهة تلقائياً — بدون كلمة implements ولا تسجيل صريح.
هذا المبدأ يُعرف بـ التنفيذ الضمني (Implicit Implementation)، وهو ما يجعل Go مختلفة عن أغلب اللغات.
تعريف واجهة
الواجهة هي مجموعة من توقيعات الأساليب (method signatures):
// واجهة الشكل — Shape interface
type Shape interface {
Area() float64
Perimeter() float64
}
أي نوع يملك أسلوبي Area() و Perimeter() بنفس التوقيعات يُحقق واجهة Shape — تلقائياً!
مثال عملي كامل
لاحظ: لم نكتب Rectangle implements Shape في أي مكان! Go اكتشف ذلك تلقائياً لأن Rectangle يملك كل الأساليب المطلوبة.
لماذا التنفيذ الضمني مهم؟
- فصل الاعتماديات — يمكنك تعريف واجهة في حزمة وتنفيذها في حزمة أخرى بدون أي استيراد
- اختبار أسهل — أنشئ أنواع وهمية (mocks) بسهولة
- مرونة — أضف واجهات لاحقاً بدون تعديل الكود الموجود
الواجهة الفارغة — Empty Interface
interface{} (أو any منذ Go 1.18) هي واجهة بدون أي أساليب. لأن كل نوع يُحقق صفر أساليب، كل نوع يُحقق الواجهة الفارغة:
تحذير: استخدام
interface{}أوanyيُفقدك أمان الأنواع. استخدمها فقط عند الضرورة (مثلfmt.Printlnالتي تقبل أي شيء).
تأكيد الأنواع — Type Assertions
عندما لديك قيمة من نوع واجهة وتريد الوصول للنوع الحقيقي:
القاعدة: دائماً استخدم الصيغة value, ok := i.(Type) لتجنب panic.
مفتاح الأنواع — Type Switch
بديل أنيق لسلسلة من تأكيدات الأنواع:
واجهات متعددة — Multiple Interfaces
نوع واحد يمكنه تحقيق عدة واجهات:
تركيب الواجهات — Interface Embedding
يمكنك بناء واجهات من واجهات أخرى (كما رأينا في ReadWriter أعلاه). هذا يُشجع على تعريف واجهات صغيرة ومركزة:
type Reader interface { Read() string }
type Writer interface { Write(string) }
type Closer interface { Close() error }
// واجهة مركبة من ثلاث واجهات — Composed from three interfaces
type ReadWriteCloser interface {
Reader
Writer
Closer
}
نصيحة Go: “كلما كانت الواجهة أصغر، كلما كانت أقوى.” واجهة بأسلوب واحد مثل io.Reader أقوى من واجهة بعشرة أساليب.
أخطاء شائعة
خطأ 1: استخدام الواجهة الفارغة في كل مكان
// ❌ تفقد أمان الأنواع
func Process(data interface{}) { ... }
// ✅ عرّف واجهة محددة
type Processor interface {
Process() Result
}
خطأ 2: نسيان أن مستقبل المؤشر لا يُحقق الواجهة للقيمة
type Saver interface { Save() }
type Doc struct{}
func (d *Doc) Save() {} // مستقبل مؤشر
var s Saver = Doc{} // ❌ خطأ تجميع! Doc لا يُحقق Saver
var s Saver = &Doc{} // ✅ *Doc يُحقق Saver