مقدمة
كتابة برمجيات قابلة للتوافر وقابلة للتكرار ومودولة هي أهمية لتطوير برمجيات متنوعة. العمل بهذه الطريقة يضمن سهولة إدارة البرمجيات بتجنب حاجة إلى إجراء نفس التغيير في أماكن عديدة. كيف تحقق هذا تختلف من لغة لأخرى. على سبيل المثال، التوريث هي طريقة شائعة تستخدم في اللغات المثل Java، C++، C#، وما إلى ذلك.
يمكن للمطورين أيضًا تحقيق نفس الأهداف التصميمية من خلال التركيب. التركيب هو طريقة لتركيب الأجسام أو الأنواع البيانية في أكثر تعقيدًا. هذا هو الطريقة التي تستخدمه Go لتعزيز التأسيس المتكرر والتجديد والمرونة. توفر الوصوليات في Go طريقة لتنظيم التركيبات المعقدة، والتعلم كيفية استخدامها سيسمح لك بإنشاء البرمجيات المتكررة والقابلة للتوافر.
في هذا المقال، سنتعلم كيفية تركيب أنواع خاصة لتمتلك سلوكيات مشتركة، وهذا سيسمح لنا بتكرار برمجياتنا. سنتعلم أيضًا كيفية تنفيذ الوصوليات لأنواعنا الخاصة التي ست满足وصوليات معينة من الحزب الآخر.
تعريف سلوك
أحد تنفيذات القوة الأساسية للتركيب هو استخدام الواجهات. تعرف الواجهة سلوك نوع معين. أحد أكثر واجهات الاستخدام في مكتبة Go القياسية هو واجهة fmt.Stringer
التالية:
السطر الأول من الكود يعرف type
يُدعى Stringer
. ثم يُقول إنه interface
. تمامًا مثل تعريف هيكل، تستخدم Go قوسين مائلين ({}
) لتحيين تعريف الواجهة. مقارنةً بتعريف الهياكل، نحدد فقط سلوك الواجهة؛ أي، “ما الذي يمكن أن يفعله هذا النوع”.
في حالة واجهة Stringer
، السلوك الوحيد هو طريقة String()
. تأخذ الطريقة لا أي مدخلات وتعيد سلسلة.
الآن، دعونا ننظر إلى بعض الكود الذي يحتوي على سلوك fmt.Stringer
:
أول شيء نفعله هو إنشاء نوع جديد يُدعى Article
. يحتوي هذا النوع على حقل Title
وحقل Author
وكلاهما من نوع السلسلة data type:
ثم، نحدد method
يُدعى String
على نوع Article
. ستعيد طريقة String
سلسلة تمثل نوع Article
:
ومن ثم في مétodo main
الخاص بنا function, ننشئ مثال للتصنيف Article
ونسميه المتغير variable المسمى a
. نقدم قيمتين "Understanding Interfaces in Go"
لحقل Title
و "Sammy Shark"
لحقل Author
:
من ثم نطبع نتيجة مétodo String
باستخدام fmt.Println
وتقديم نتيجة ممارسة a.String()
:
بعد تشغيل البرنامج سترى تلك الخريطة التالية:
OutputThe "Understanding Interfaces in Go" article was written by Sammy Shark.
حتى الآن لم نستخدم تقاطع بل ننشأ نوعًا ما بهذا السلوك. تطابق هذا السلوك لتقاطع fmt.Stringer
. من ثم دعونا نرى كيف يمكننا استخدام هذا السلوك لجعل برمجتنا أكثر تكرارًا.
Defining an Interface
حالما نحن نحدد النوع بالسلوك المطلوب، يمكننا النظر إلى كيفية استخدام هذا السلوك.
ولكن قبل ذلك، دعونا ننظر فيما سيحتاج إليه إذا أردنا أن نصل إلى مétodo String
من نوع Article
في مétodo آخر:
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
ولأنه كذلك ، يمكننا بدلاً من ذلك تعريف واجهة للمنتقل إليها:
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
:
ولواحد واحد فقط يوجد في واجهة Stringer
وهو دالة String()
التي تعود بنوع string
ويمكن تعريف المتواجد كمتوالي للعمليات الخاصة بالتلك التي تحدد من أجل النوع المحدد في Go. بخلاف المتوالي ، يمكن للمتوالي المعين أن يُطلق من خلال محل النسخ المعين للتلك التي تم تعريفها عليه.
ومن ثم نحن نريد تحديد توقيع Print
الطريقة للاستبدال بواجهة Stringer
وليس نوعًا ما من التصنيف الواقعي لـArticle
لأن المستند يعرف أن واجهة Stringer
تحدد الدالة String
، سيقبل أي أنواع تحدد أيضًا المتوالي String
لها.
ومن الآن يمكننا استخدام طريقة Print
مع أي شيء ي满意度 Stringer
الواجهة. دعونا نخلق نوعًا آخر للإظهار هذا الشيء:
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
هذه الواجهة.
OutputThe "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
. سيعود هذا المتد بمساحة هندسية لشكلهما الخاص:
لأن كل نوع يدeclare المتد Area
، يمكننا إنشاء واجهة تحدد هذا التصرف. نحن نصنع المتد المسمى Sizer
التالي:
ثم نعرف معادة تدعى Less
تأخذ عندين Sizer
وتسترجع الأقل الكبير.
لاحظوا أنه ليس فقط نحن نقبل المقاربتين كنوع Sizer
، ولكن نسترجع أيضًا النتيجة كنوع Sizer
أيضًا. هذا يعني أنه لم يعد يعود بالدوائر أو المربع، ولكن واجهة Sizer
الإجراء.
وأخيرًا، سنطبع ما كان لديه المساحة الأصغر:
Output{Width:5 Height:10} is the smallest
الآن، دعونا نإضافة سلوك آخر إلى كل نوع. هذه المرة سنإضافة 方法 String()
التي تعود بالنص. سيكون هذا ي满足了 fmt.Stringer
接口:
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
:
ملاحظة: يعتبر من الطبيعي حاول تسمية معاييرك بتوقف بـ er
, مثل fmt.Stringer
، io.Writer
و المزيد. لهذا أطلقنا أسم معاييرنا Shaper
و ليس Shape
، و ليس معاييرنا الأولي.
حالياً يمكننا إنشاء وظيفة تدعى PrintArea
التي تأخذ Shaper
كما محاورة. هذا يعني أنه يمكننا أن نسمع كلاً الأدوات على القيمة المتماسكة لكل من ال method Area
و String
:
إذا أجرينا هذا البرنامج، سنحصل على الخريطة التالية:
Outputarea 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