كيفية استخدام الواجهات في لغة Go

مقدمة

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

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

في هذا المقال، سنتعلم كيفية تركيب أنواع خاصة لتمتلك سلوكيات مشتركة، وهذا سيسمح لنا بتكرار برمجياتنا. سنتعلم أيضًا كيفية تنفيذ الوصوليات لأنواعنا الخاصة التي ست满足وصوليات معينة من الحزب الآخر.

تعريف سلوك

أحد تنفيذات القوة الأساسية للتركيب هو استخدام الواجهات. تعرف الواجهة سلوك نوع معين. أحد أكثر واجهات الاستخدام في مكتبة Go القياسية هو واجهة fmt.Stringer التالية:

type Stringer interface {
    String() string
}

السطر الأول من الكود يعرف type يُدعى Stringer. ثم يُقول إنه interface. تمامًا مثل تعريف هيكل، تستخدم Go قوسين مائلين ({}) لتحيين تعريف الواجهة. مقارنةً بتعريف الهياكل، نحدد فقط سلوك الواجهة؛ أي، “ما الذي يمكن أن يفعله هذا النوع”.

في حالة واجهة Stringer، السلوك الوحيد هو طريقة String(). تأخذ الطريقة لا أي مدخلات وتعيد سلسلة.

الآن، دعونا ننظر إلى بعض الكود الذي يحتوي على سلوك fmt.Stringer:

main.go
package main

import "fmt"

type Article struct {
	Title string
	Author string
}

func (a Article) String() string {
	return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}

func main() {
	a := Article{
		Title: "Understanding Interfaces in Go",
		Author: "Sammy Shark",
	}
	fmt.Println(a.String())
}

أول شيء نفعله هو إنشاء نوع جديد يُدعى Article. يحتوي هذا النوع على حقل Title وحقل Author وكلاهما من نوع السلسلة data type:

main.go
...
type Article struct {
	Title string
	Author string
}
...

ثم، نحدد method يُدعى String على نوع Article. ستعيد طريقة String سلسلة تمثل نوع Article:

main.go
...
func (a Article) String() string {
	return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}
...

ومن ثم في مétodo main الخاص بنا function, ننشئ مثال للتصنيف Article ونسميه المتغير variable المسمى a. نقدم قيمتين "Understanding Interfaces in Go" لحقل Title و "Sammy Shark" لحقل Author:

main.go
...
a := Article{
	Title: "Understanding Interfaces in Go",
	Author: "Sammy Shark",
}
...

من ثم نطبع نتيجة مétodo String باستخدام fmt.Println وتقديم نتيجة ممارسة a.String():

main.go
...
fmt.Println(a.String())

بعد تشغيل البرنامج سترى تلك الخريطة التالية:

Output
The "Understanding Interfaces in Go" article was written by Sammy Shark.

حتى الآن لم نستخدم تقاطع بل ننشأ نوعًا ما بهذا السلوك. تطابق هذا السلوك لتقاطع fmt.Stringer. من ثم دعونا نرى كيف يمكننا استخدام هذا السلوك لجعل برمجتنا أكثر تكرارًا.

Defining an Interface

حالما نحن نحدد النوع بالسلوك المطلوب، يمكننا النظر إلى كيفية استخدام هذا السلوك.

ولكن قبل ذلك، دعونا ننظر فيما سيحتاج إليه إذا أردنا أن نصل إلى مétodo String من نوع Article في مétodo آخر:

main.go
package main

import "fmt"

type Article struct {
	Title string
	Author string
}

func (a Article) String() string {
	return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}

func main() {
	a := Article{
		Title: "Understanding Interfaces in Go",
		Author: "Sammy Shark",
	}
	Print(a)
}

func Print(a Article) {
	fmt.Println(a.String())
}

في هذا الشيء نضيف ما يسمى بـPrint التالي وهو ما يتم إدخاله كما يليق مع المصطلح Article كما تلاحظ أن وظيفة Print الوحيد ما يفعله هو من دعم دالة String ولأنه كذلك ، يمكننا بدلاً من ذلك تعريف واجهة للمنتقل إليها:

main.go
package main

import "fmt"

type Article struct {
	Title string
	Author string
}

func (a Article) String() string {
	return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}

type Stringer interface {
	String() string
}

func main() {
	a := Article{
		Title: "Understanding Interfaces in Go",
		Author: "Sammy Shark",
	}
	Print(a)
}

func Print(s Stringer) {
	fmt.Println(s.String())
}

لقد قمنا بإنشاء واجهة تدعى Stringer:

main.go
...
type Stringer interface {
	String() string
}
...

ولواحد واحد فقط يوجد في واجهة Stringer وهو دالة String() التي تعود بنوع string ويمكن تعريف المتواجد كمتوالي للعمليات الخاصة بالتلك التي تحدد من أجل النوع المحدد في Go. بخلاف المتوالي ، يمكن للمتوالي المعين أن يُطلق من خلال محل النسخ المعين للتلك التي تم تعريفها عليه.

ومن ثم نحن نريد تحديد توقيع Print الطريقة للاستبدال بواجهة Stringer وليس نوعًا ما من التصنيف الواقعي لـArticle لأن المستند يعرف أن واجهة Stringer تحدد الدالة String ، سيقبل أي أنواع تحدد أيضًا المتوالي String لها.

ومن الآن يمكننا استخدام طريقة Print مع أي شيء ي满意度 Stringer الواجهة. دعونا نخلق نوعًا آخر للإظهار هذا الشيء:

main.go
package main

import "fmt"

type Article struct {
	Title  string
	Author string
}

func (a Article) String() string {
	return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}

type Book struct {
	Title  string
	Author string
	Pages  int
}

func (b Book) String() string {
	return fmt.Sprintf("The %q book was written by %s.", b.Title, b.Author)
}

type Stringer interface {
	String() string
}

func main() {
	a := Article{
		Title:  "Understanding Interfaces in Go",
		Author: "Sammy Shark",
	}
	Print(a)

	b := Book{
		Title:  "All About Go",
		Author: "Jenny Dolphin",
		Pages:  25,
	}
	Print(b)
}

func Print(s Stringer) {
	fmt.Println(s.String())
}

نقوم الآن بإضافة نوع ثان يدعى Book وهو يمتلك أيضًا دالة String وهذا يعني أنه يستند أيضًا على الواجهة Stringer وبسبب هذا يمكننا أيضًا إرساله إلى متطلب خاص بـPrint هذه الواجهة.

Output
The "Understanding Interfaces in Go" article was written by Sammy Shark. The "All About Go" book was written by Jenny Dolphin. It has 25 pages.

حتى الآن، لقد أظهرنا كيفية استخدام واحد فقط للواجهة. ومع ذلك ، يمكن للواجهة أن تكون لها أكثر من تصرف واحد معين. في المرة القادمة سنرى كيف يمكننا جعل واجهاتنا أكثر قابلية بتعريف مétodos إضافيين.

تعريف تصرفات متعددة في الواجهة

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

main.go
package main

import (
	"fmt"
	"math"
)

type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return math.Pi * math.Pow(c.Radius, 2)
}

type Square struct {
	Width  float64
	Height float64
}

func (s Square) Area() float64 {
	return s.Width * s.Height
}

type Sizer interface {
	Area() float64
}

func main() {
	c := Circle{Radius: 10}
	s := Square{Height: 10, Width: 5}

	l := Less(c, s)
	fmt.Printf("%+v is the smallest\n", l)
}

func Less(s1, s2 Sizer) Sizer {
	if s1.Area() < s2.Area() {
		return s1
	}
	return s2
}

لأن كل نوع يدeclare المتد Area، يمكننا إنشاء واجهة تحدد هذا التصرف. نحن نصنع المتد المسمى Sizer التالي:

main.go
...
type Sizer interface {
	Area() float64
}
...

ثم نعرف معادة تدعى Less تأخذ عندين Sizer وتسترجع الأقل الكبير.

main.go
...
func Less(s1, s2 Sizer) Sizer {
	if s1.Area() < s2.Area() {
		return s1
	}
	return s2
}
...

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

وأخيرًا، سنطبع ما كان لديه المساحة الأصغر:

Output
{Width:5 Height:10} is the smallest

الآن، دعونا نإضافة سلوك آخر إلى كل نوع. هذه المرة سنإضافة 方法 String() التي تعود بالنص. سيكون هذا ي满足了 fmt.Stringer 接口:

main.go
package main

import (
	"fmt"
	"math"
)

type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return math.Pi * math.Pow(c.Radius, 2)
}

func (c Circle) String() string {
	return fmt.Sprintf("Circle {Radius: %.2f}", c.Radius)
}

type Square struct {
	Width  float64
	Height float64
}

func (s Square) Area() float64 {
	return s.Width * s.Height
}

func (s Square) String() string {
	return fmt.Sprintf("Square {Width: %.2f, Height: %.2f}", s.Width, s.Height)
}

type Sizer interface {
	Area() float64
}

type Shaper interface {
	Sizer
	fmt.Stringer
}

func main() {
	c := Circle{Radius: 10}
	PrintArea(c)

	s := Square{Height: 10, Width: 5}
	PrintArea(s)

	l := Less(c, s)
	fmt.Printf("%v is the smallest\n", l)

}

func Less(s1, s2 Sizer) Sizer {
	if s1.Area() < s2.Area() {
		return s1
	}
	return s2
}

func PrintArea(s Shaper) {
	fmt.Printf("area of %s is %.2f\n", s.String(), s.Area())
}

لأنه يتم تنفيذ كلاً Circle و النوع Square يتم تنفيذ كلاً من ال methods Area و String، يمكننا حالياً إنشاء واجهة أخرى لتوصيف مجموعة أوسع من السلوك. لقيام بذلك، سنقوم بإنشاء واجهة تدعى Shaper. سنترك هذه من خلال الواجهة Sizer و الواجهة fmt.Stringer:

main.go
...
type Shaper interface {
	Sizer
	fmt.Stringer
}
...

ملاحظة: يعتبر من الطبيعي حاول تسمية معاييرك بتوقف بـ er, مثل fmt.Stringer، io.Writer و المزيد. لهذا أطلقنا أسم معاييرنا Shaper و ليس Shape، و ليس معاييرنا الأولي.

حالياً يمكننا إنشاء وظيفة تدعى PrintArea التي تأخذ Shaper كما محاورة. هذا يعني أنه يمكننا أن نسمع كلاً الأدوات على القيمة المتماسكة لكل من ال method Area و String:

main.go
...
func PrintArea(s Shaper) {
	fmt.Printf("area of %s is %.2f\n", s.String(), s.Area())
}

إذا أجرينا هذا البرنامج، سنحصل على الخريطة التالية:

Output
area of Circle {Radius: 10.00} is 314.16 area of Square {Width: 5.00, Height: 10.00} is 50.00 Square {Width: 5.00, Height: 10.00} is the smallest

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

على سبيل المثال، إذا مررنا بـShaper إلى منظومة Less، قد نفترض أنه سيستمر في مناسبة كل من الArea والString طرق. ومع ذلك، لأننا نريد فقط أن نستخدم طريقة Area، يجعل المنظومة Less واضحة، لأننا نعلم بأنه يمكننا فقط أن نستخدم طريقة Area لأي ما يتم تقديمه له.

نهاية

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

إذا كنت تريد أن تتعلم المزيد عن لغة البرمجيات Go، انظر إلى السلسلة الكاملة من سلسلة كيف تكون في Go.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-interfaces-in-go