הסתרת מידע ב-Go
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 50 מתוך 107.
הסתרת מידע היא יותר מסתם הפיכת שדות ללא מיוצאים. מדובר בתכנון הטיפוסים שלך כך שקוד חיצוני יכיר רק את מה שהוא צריך להכיר. לא יותר.
המטרה היא לחשוף התנהגות באמצעות מתודות, תוך שמירה על כך שהמבנה הפנימי יישאר מוסתר לחלוטין. שקלו את הגישה הזאת:
type Counter struct {
value int
maxLimit int
}
func NewCounter(limit int) *Counter {
return &Counter{maxLimit: limit}
}
func (c *Counter) Increment() bool {
if c.value >= c.maxLimit {
return false
}
c.value++
return true
}
func (c *Counter) Value() int {
return c.value
}
קוד חיצוני לא יודע ש־Counter משתמש ב־int באופן פנימי. הוא יכול להיות float64, פרוסה או משהו אחר לגמרי. אפשר לשנות את המימוש בלי להשפיע על שום קוד שמשתמש ב־Counter, כל עוד המתודות מתנהגות באותה צורה.
העיקרון הזה חל גם על פונקציות עזר. השאירו את הלוגיקה הפנימית לא מיוצאת:
// מיוצא - חלק מה-API הציבורי
func (c *Counter) Reset() {
c.value = 0
}
// לא מיוצא - פונקציית עזר פנימית
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
על ידי הסתרת isAtLimit(), אפשר לשנות או להסיר אותה בהמשך. הסתרת מידע יוצרת גבול ברור בין מה שהחבילה שלך מבטיחה למשתמשים חיצוניים לבין אופן הפעולה הפנימי שלה.
אתגר
קלבואו נבנה מערכת ארנק מאובטחת שמדגימה הסתרת מידע. תיצרו ארנק דיגיטלי שבו מעקב אחר היתרה הפנימית והיסטוריית העסקאות מוסתרים לחלוטין מקוד חיצוני: רק ההתנהגות נחשפת באמצעות מתודות.
תארגנו את הקוד שלכם בשני קבצים:
wallet.go: צרו מבנהWalletשמסתיר את כל המצב הפנימי שלו. הארנק צריך לעקוב באופן פנימי אחר היתרה הנוכחית ומגבלת ההוצאה, אך קוד חיצוני לא אמור לדעת כיצד הם מאוחסנים או מיוצגים. חשפו התנהגות באמצעות המתודות האלה:NewWallet(limit float64) *Wallet- בנאי שיוצר ארנק עם מגבלת ההוצאה הנתונה ויתרה של אפסDeposit(amount float64) bool- מוסיפה כסף לארנק, ומחזירהtrueאם הפעולה הצליחה (הסכום חייב להיות חיובי)Spend(amount float64) bool- מוציאה כסף אם יש יתרה מספקת וגם הסכום אינו חורג ממגבלת ההוצאה, ומחזירהtrueאם הפעולה הצליחהBalance() float64- מחזירה את היתרה הנוכחיתStatus() string- מחזירה הודעת מצב בתבנית:Balance: $[balance] (Limit: $[limit])
main.go: קראו את הגדרות הארנק והעסקאות מהקלט, צרו ארנק, בצעו פעולות והציגו את התוצאות לאחר כל פעולה.
הקלטים הבאים יסופקו:
- שורה 1: מגבלת הוצאה
- שורה 2: סכום ההפקדה
- שורה 3: סכום ההוצאה הראשונה
- שורה 4: סכום ההוצאה השנייה
לאחר יצירת הארנק, הדפיסו את מצבו ההתחלתי. לאחר מכן בצעו כל פעולה והדפיסו את המצב המעודכן (אם הפעולה הצליחה) או את Transaction failed (אם הפעולה נדחתה). עצבו את כל הסכומים בדולרים עם שתי ספרות אחרי הנקודה העשרונית.
לדוגמה, עבור 50, 100, 30 ו-80, הפלט שלכם אמור להיות:
Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failedהעסקה האחרונה נכשלת כי $80 חורגים ממגבלת ההוצאה של $50, אף על פי שיש יתרה מספקת. הארנק אוכף את הכללים שלו באופן פנימי מבלי לחשוף כיצד הוא מקבל את ההחלטות האלה. הקוד החיצוני פשוט קורא למתודות ומקבל תוצאות, מבלי לדעת אי פעם את פרטי המימוש הפנימיים.
נסו בעצמכם
package main
import (
"fmt"
)
func main() {
// קרא את הקלטים
var limit float64
var depositAmount float64
var spend1 float64
var spend2 float64
fmt.Scanln(&limit)
fmt.Scanln(&depositAmount)
fmt.Scanln(&spend1)
fmt.Scanln(&spend2)
// TODO: צור ארנק חדש עם מגבלת ההוצאה הנתונה
// TODO: הדפס את המצב ההתחלתי
// TODO: בצע הפקדה והדפס את התוצאה (מצב או "Transaction failed")
// TODO: בצע את ההוצאה הראשונה והדפס את התוצאה
// TODO: בצע את ההוצאה השנייה והדפס את התוצאה
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין