איך להשתמש בממשקים בגוא

הקדמה

כתובת קוד גמיש, משתמשים ומודולארים הוא חיוני לפיתוח תוכנות גמישות. עבודה בדרך זו מובטחת שהקוד יהיה קל יותר לשמר דרך הדילגמניה מהצורך בשינוי אותו במקומות רבים. איך שתעשה את זה משנה מדובר בשפה במידה מסויימת. לדוגמה, היררכיה היא דרך רגילה שמשמשת בשפות כמו ג' אווה, C++, C# ועוד.

מפתחים יכולים גם להשיג את אותם מטרות העיצוב באמצעות התצרף. התצרף הוא דרך לשילב עצמים או מסוגי מידע לאחדים מורכבים יותר. זו הדרך שגו משתמש בה לעדד שימוש בקוד, מודולריות וגמישות. הממשקים בגו מספקים שיטה לארגון התצרפויות מורכבות, ולמדינות איך להשתמש בהם יתאפשר לך ליצור קוד משובץ נפוץ ומוחזר יכול.

במאמר זה, נלמד איך לתת צורות מותאמות אישית שיש התנהגויות משותפות, שיאפשר לנו לשימוש מחדש בקוד שלנו. נלמד גם איך ליישם ממשקים עבור הצורות האישיות שלנו שיספקו את הממשקים המוגדרים מתוך ערימה אחרת.

הגדרה של התנהגות

אחד מהיישומים הבסיסיים של הקומפוזציה הוא שימוש בממשקים. ממשק מגדיר התנהגות של סוג. אחד מהממשקים הכי נפוצים בספרית הסטנדרט של Go הוא הממשק fmt.Stringer:

type Stringer interface {
    String() string
}

השורה הראשונה בקוד מגדירה סוג שנקרא Stringer. אחר כך היא מצטטה שהיא ממשק משולש. כמו בהגדרת מבנה, 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 ושניהם מסוג שטרים:מינורמל:

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)
}
...

אז, בmain function שלנו, אנחנו יוצרים מקום מסוג Article ואושרים אותו למשתנה שנקרא a. אנחנו מעניקים את הערכים "Understanding Interfaces in Go" לשדה Title ו"Sammy Shark" לשדה Author:

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

אז, אנחנו מדפיסים את תוצאת השיטה 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

עכשיו שיצרנו את הסוג עם התנהגות הרצועה, אנחנו יכולים להסתכל על איך להשתמש בתנהגות זו.

לפני שנעשה את זה, בואו נסתכל מה אנחנו יכולים לעשות אם נרצה לקרוא את השיטה String מסוג Article בפונקציה:

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. שיחה היא פונקצייה מיוחדת שמוגבלת בסוג מסויים בגו. שונה מהפונקצייה, השיחה יכולה להיות קרואה רק מהמקבל של הסוג שבו היא נוצרה.

אחר כך אנחנו מעדכנים את החתימה של השיחה 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.

עד כה, הראינו איך להשתמש במשתמש אחד בלבד. אף על פי שלמשתמש יכול להיות יותר מאחד התנהגות מוגדרה. בהמשך, נראה איך אנחנו יכולים להפוך את המשתמשים שלנו ליותר גיווניים על-ידי הצעת יותר פעמים של שימוש בשיטות.

התנהגויות רבות במשתמש

אחד העקרונות המרכזיים בכתיבת קוד גו הוא לכתוב סוגים קטנים וקצרים ולהרכיב אותם בכדי ליצור סוגים גדולים יותר ומורכבים. אותו הדבר נכון גם כשאנחנו מרכיבים משתמשים. כדי לראות איך אנחנו בונים משתמש, נתחיל על-ידי הגדרת משתמש אחד בלבד. נהגדר שני צורות, גלולה וריבוע, ושניהם יוגדרו שירות בשם 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
}

בגלל שכל סוג מדeclares the Area method, we can create an interface that defines that behavior. We create the following Sizer interface:

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

We then define a function called Less that takes two Sizer and returns the smallest one:

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

Notice that we not only accept both arguments as the type Sizer, but we also return the result as a Sizer as well. This means that we no longer return a Square or a Circle, but the interface of Sizer.

Finally, we print out what had the smallest area:

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 מביאים שני השיטות Area ו String, אנחנו יכולים עכשיו ליצור אינטרפאציה נוספת שתואמת עם קבוצה רחבה יותר של התנהגויות. בכדי לעשות את זה, ניצור אינטרף שנקרא Shaper. אנחנו נרכש את זה מהאינטרף Sizer והאינטרף fmt.Stringer:

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

הערה: נחשב זאת כאידיאוטית לנסות לקרוא לאינטרף שלך בסוף ב er, כמו fmt.Stringer, io.Writer, וכו '. זו הסיבה שנקרא לאינטרף שלנו Shaper ולא Shape.

עכשיו נוכל ליצור פונקציה שנקראת PrintArea שמקבלת אינטרף Shaper כאן. זה אומר שאנחנו יכולים לקרוא לשני השיטות על הערך שמעבר בשביל של שיטות 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 של כל אג'נדה שנעבירה לו.

הסיכוי

ראינו איך בניית ממשקים קטנים ובנייתם למעלה למקבלים גדולים מאפשרת לנו לשיתף רק מה שאנחנו צריכים לפעולה או שיטה. למדנו גם שאנחנו יכולים להרכיב את הממשקים שלנו מממשקים אחרים, כולל אלה שנוצרים מערכות אחרות, לא רק מהערכות שלנו.

אם תרצו ללמוד עוד על השפה הגונגו, בואו ובחנו את כל את סדרת ההוראות "איך לקוד בגונגו".

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