في أبسط صورها، نوع الواجهة هو مجموعة من توقيعات الطرق.
إليك مثالاً على تعريف واجهة تتضمن طريقتين Add و Value:
type Counter interface {
Add(increment int)
Value() int
}
يمكن حذف أسماء المعاملات مثل increment من تعريف الواجهة، لكنها غالباً ما تزيد من سهولة القراءة.
لا تحتوي أسماء الواجهات في Go على كلمة Interface أو I.
بل غالباً ما تنتهي بـ er، مثل Reader وStringer.
أي نوع يعرّف طرق الواجهة فإنه "ينفذ" الواجهة تلقائياً وبشكل ضمني.
لا توجد كلمة مفتاحية implements في Go.
النوع التالي ينفذ واجهة Counter التي رأيناها أعلاه.
type Stats struct {
value int
// ...
}
func (s Stats) Add(v int) {
s.value += v
}
func (s Stats) Value() int {
return s.value
}
func (s Stats) SomeOtherMethod() {
// The type can have additional methods not mentioned in the interface.
}
لتنفيذ الواجهة، لا يهم ما إذا كانت الطريقة تستقبل قيمتها عبر مستقبِل قيمة أو مستقبِل مؤشر. (راجع مفاهيم الطرق إذا لم تكن متأكداً بشأنها.)
يمكن لقيمة من نوع واجهة أن تحمل أي قيمة تنفذ تلك الطرق. 1
هذا يعني أنه يمكن الآن استخدام Stats في كل المواضع التي تتوقع واجهة Counter.
func SetUpAnalytics(counter Counter) {
// ...
}
stats := Stats{}
SetUpAnalytics(stats)
// works because Stats implements Counter
لو كانت جميع معاملات الدوال وقيم الإرجاع بحاجة إلى استخدام أنواع ملموسة (أي غير واجهة)، لكان ذلك يحدّ بشدة من قابليتها لإعادة الاستخدام. وباستخدام أنواع الواجهات للمعاملات، لا يُعرَّف سوى السلوك اللازم لتؤدي الدالة عملها. الآن يمكن استخدام الدالة مع معاملات من أنواع ملموسة مختلفة، ما دامت تلك الأنواع الملموسة تنفذ الواجهة. والمنطق نفسه ينطبق عند استخدام أنواع الواجهات كقيم إرجاع.
ولأن الواجهات تُنفَّذ ضمنياً، يمكن للنوع أن ينفذ واجهات متعددة بسهولة. كل ما يحتاجه هو تعريف جميع الطرق اللازمة.
يمكن أن يتضمن تعريف الواجهة واجهات أخرى. إليك مثالاً من المكتبة القياسية.
type ReadCloser interface {
Reader
Closer
}
// Based on these interfaces:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
فبدلاً من تكرار توقيعات الطرق لـ Read وClose لتعريف ReadCloser، يستفيد تعريف الواجهة من التعريفات الموجودة لـ Reader وCloser.
يوجد في Go نوع واجهة واحد مميز جداً، هو نوع الواجهة الفارغة الذي لا يحتوي على أي طرق.
تُكتب الواجهة الفارغة هكذا: interface{}.
وفي Go 1.18 أو أحدث، يمكن استخدام any أيضاً.
وقد عُرّفت كاسم مستعار.
ولأن الواجهة الفارغة لا تحتوي على طرق، فإن كل نوع ينفذها ضمنياً. وهذا مفيد لتعريف دالة يمكنها قبول أي قيمة بشكل عام. في هذه الحالة، يستخدم معامل الدالة نوع الواجهة الفارغة.
على سبيل المثال:
func AnythingGoes(i interface{}) string {
if i == 0 {
return "zero"
}
if i == "" {
return "empty string"
}
return "something else"
}