תבנית המתאם
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 92 מתוך 107.
תבנית המתאם מאפשרת לממשקים שאינם תואמים לעבוד יחד על ידי עטיפת טיפוס קיים בממשק חדש. בעוד ש-Command עוטפת פעולות, Adapter מתרגמת ממשק אחד לאחר שהלקוחות מצפים לו.
דמיין שיש לך מדפסת ישנה שמשתמשת בחתימת מתודה שונה מזו שהיישום שלך מצפה לה:
// ממשק היעד שבו היישום שלך משתמש
type Printer interface {
Print(text string) string
}
// מדפסת ישנה עם ממשק לא תואם
type OldPrinter struct{}
func (o OldPrinter) PrintDocument(doc string, copies int) string {
return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}המתאם עוטף את הטיפוס הישן ומממש את הממשק המצופה:
type OldPrinterAdapter struct {
oldPrinter OldPrinter
}
func (a OldPrinterAdapter) Print(text string) string {
return a.oldPrinter.PrintDocument(text, 1)
}כעת המדפסת הישנה פועלת בצורה חלקה עם קוד שמצפה לממשק Printer:
func PrintMessage(p Printer, msg string) string {
return p.Print(msg)
}
adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello") // "Old printer: Hello (x1)"תבנית המתאם חשובה מאין כמוה כשמשלבים ספריות של צד שלישי, עובדים עם קוד ישן או כשצריך להשתמש במחלקות קיימות שאינן תואמות לדרישות הממשק שלך. היא משמשת כגשר בלי לשנות את הקוד המקורי.
אתגר
קלבואו נבנה מערכת לניגון מדיה באמצעות תבנית המתאם (Adapter)! יש לך נגן שמע מודרני שמצפה לממשק אחיד, אבל עליך לשלב ציוד שמע ישן שמשתמש בחתימות מתודות שונות לחלוטין. המתאם שלך יגשר על הפער הזה ויאפשר לחומרה הישנה לעבוד בצורה חלקה עם המערכת החדשה.
תארגן את הקוד שלך בשלושה קבצים:
player.go: הגדר את ממשק היעד שלך ואת התקן השמע הישן.צור ממשק
AudioPlayerשהמערכת המודרנית שלך מצפה לו, עם מתודהPlay(track string) stringשמקבלת את שם הרצועה ומחזירה הודעה על הניגון.הגדר גם התקן ישן שאינו תואם לממשק הזה:
- מבנה
VinylPlayerעם שדהSpeedRPMמסוג int. יש לו מתודהDropNeedle(record string, side string) stringשמחזירהPlaying [record] ([side] side) at [speed]rpm
הממשק של VinylPlayer אינו תואם ל-AudioPlayer. הוא דורש שני פרמטרים ומשתמש בשם מתודה שונה לחלוטין.
- מבנה
adapter.go: צור את המתאם שלך, שהופך את ההתקן הישן לתואם.בנה מבנה
VinylAdapterשעוטףVinylPlayerומממש את הממשקAudioPlayer. כשקוראים ל-Play, המתאם צריך לקרוא למתודהDropNeedleשל נגן התקליטים, ולהשתמש כברירת מחדל ב-Aעבור הפרמטר של הצד.צור גם נגן מודרני לשם השוואה:
- מבנה
DigitalPlayer: המתודהPlayשלו מחזירהStreaming: [track]
- מבנה
main.go: הדגם איך שני הנגנים פועלים באמצעות אותו ממשק.צור פונקציה
PlayMusic(player AudioPlayer, track string) stringשמשתמשת בממשק כדי לנגן מוזיקה. הפונקציה לא צריכה לדעת או להתעניין אם היא משתמשת בנגן דיגיטלי או בנגן תקליטים שהותאם באמצעות מתאם.קרא את סוג הנגן (
digitalאוvinyl), ואם הוא נגן תקליטים, קרא גם את מהירות ה-RPM. לאחר מכן קרא את שם הרצועה. צור את הנגן המתאים (באמצעות המתאם עבור נגן תקליטים), נגן את הרצועה באמצעות הפונקציהPlayMusicשלך והדפס את התוצאה.
הקלטים הבאים יסופקו:
- שורה 1: סוג הנגן (
digitalאוvinyl) - שורה 2: מהירות RPM (רק אם מדובר בנגן תקליטים)
- השורה האחרונה: שם הרצועה לניגון
לדוגמה, עבור הקלט:
digital
Bohemian Rhapsodyהפלט שלך צריך להיות:
Streaming: Bohemian Rhapsodyועבור הקלט:
vinyl
33
Abbey Roadהפלט שלך צריך להיות:
Playing Abbey Road (A side) at 33rpmועבור הקלט:
vinyl
45
Blue Mondayהפלט שלך צריך להיות:
Playing Blue Monday (A side) at 45rpmשים לב שפונקציית PlayMusic פועלת באופן זהה עם שני הנגנים. אין לה מושג שנגן התקליטים למעשה מותאם מממשק שונה לחלוטין. המתאם מתרגם מאחורי הקלעים את הקריאות לממשק המודרני לפורמט הישן!
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// PlayMusic משתמשת בממשק AudioPlayer כדי להשמיע מוזיקה
// פונקציה זו פועלת עם כל מימוש של AudioPlayer
// TODO: ממשו את הפונקציה כך שתקרא ל-player.Play(track) ותחזיר את התוצאה
func PlayMusic(player AudioPlayer, track string) string {
// TODO: ממשו את הפונקציה הזו
return ""
}
func main() {
reader := bufio.NewReader(os.Stdin)
// קראו את סוג הנגן
playerType, _ := reader.ReadString('\n')
playerType = strings.TrimSpace(playerType)
var player AudioPlayer
var track string
if playerType == "vinyl" {
// קראו את מהירות ה-RPM של תקליט הוויניל
rpmStr, _ := reader.ReadString('\n')
rpmStr = strings.TrimSpace(rpmStr)
rpm, _ := strconv.Atoi(rpmStr)
// קראו את שם הרצועה
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: צרו VinylPlayer עם ערך ה-RPM הנתון
// TODO: עטפו אותו ב-VinylAdapter
// TODO: הקצו אותו למשתנה player
_ = rpm // הסירו שורה זו כשתשתמשו ב-rpm
} else {
// קראו את שם הרצועה
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: צרו DigitalPlayer
// TODO: הקצו אותו למשתנה player
}
// TODO: קרא ל-PlayMusic והדפס את התוצאה
_ = player // הסר את השורה הזו כשאתה משתמש ב-player
_ = track // הסר את השורה הזו כשאתה משתמש ב-track
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי2העמקה בסוגים וב-Structs
סוגים בסיסיים ומורכביםהגדרות של סוגים מותאמים אישיתתגיות StructStructs אנונימייםStructs מקונניםערכי אפס וברירות מחדלסיכום – ספר אנשי קשר5קומפוזיציה במקום ירושה
למה ב-Go אין ירושהיסודות הטמעת Structקידום מתודותהטמעת כמה Structsהטמעה לעומת אגרגציההסתרת מתודות מוטמעותסיכום – היררכיית עובדים8טיפול בשגיאות ו-OOP
ממשק errorסוגי שגיאות מותאמים אישיתעטיפת שגיאות (fmt.Errorf)שגיאות Sentinelerrors.Is() ו-errors.As()Panic, Defer ו-Recoverסיכום – מנתח קבצים11ספרייה סטנדרטית ותכנות מונחה עצמים
io.Reader ו-io.Writersort.Interfaceהממשק fmt.Stringerencoding/json עם מבניםהממשק http.Handlerסיכום – מודלים של REST API14תבניות עיצוב חלק 2
תבנית הפקודהתבנית המתאםתבנית העיצובתבנית שיטת התבניתתבנית המצבתבנית המורכבMiddleware בתור Decorator3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין