מערכת בנקאית
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 106 מתוך 107.
אתגר
קלבואו נבנה מערכת בנקאית שמנהלת חשבונות, עסקאות ופעולות פיננסיות! תיצור מערכת חזקה שבה שלמות הנתונים היא קריטית: יתרות החשבונות צריכות להשתנות רק באמצעות מתודות שעברו אימות, ויש לטפל בשגיאות כמו משיכת יתר או סכומים לא תקינים בצורה מסודרת.
תארגן את הקוד שלך בחמישה קבצים:
account.go: הגדר ממשקTransactableעם המתודותDeposit(amount float64) errorו-Withdraw(amount float64) error. לאחר מכן, צור שני סוגי חשבונות שמממשים את הממשק הזה:CheckingAccountעם השדות הלא מיוצאיםid,holderו-balance. חשבונות עו״ש מאפשרים משיכות שמביאות את היתרה בדיוק לאפס, אך לא מתחתיו.SavingsAccountעם אותם שדות לא מיוצאים, וכן שדהminBalance. חשבונות חיסכון חייבים לשמור על יתרת מינימום של 100.0: משיכות שיורידו את היתרה מתחת לסף הזה צריכות להיכשל.שני סוגי החשבונות זקוקים לבנאים (
NewCheckingAccountו-NewSavingsAccount) ולמתודת getter בשםBalance() float64. בהפקדות, דחה סכומים שליליים או אפס. במשיכות, דחה סכומים שליליים או אפס וכן סכומים שעולים על הכספים הזמינים.transaction.go: צור מבנהTransactionלתיעוד הפעילות בחשבון. לכל עסקה ישType(מחרוזת: "deposit" או "withdrawal"),Amount(float64) ו-AccountID(מחרוזת). הוסף בנאי בשםNewTransactionומתודה בשםString() stringשמחזירה את הפורמט:[Type] $[Amount] on [AccountID], כאשר הסכום מעוצב עם שתי ספרות אחרי הנקודה העשרונית.bank.go: בנה מבנהBankשמנהל כמה חשבונות ומתעד עסקאות. אחסן את החשבונות במפה לפי מזהה ושמור פרוסה של עסקאות. ממש את הפעולות הבאות:NewBank() *Bank: מאתחלת אוספים ריקיםAddAccount(id string, account Transactable): רושמת חשבוןDeposit(accountID string, amount float64) error: מפקידה לחשבון ומתעדת את העסקה במקרה של הצלחהWithdraw(accountID string, amount float64) error: מושכת מחשבון ומתעדת את העסקה במקרה של הצלחהTransfer(fromID, toID string, amount float64) error: מעבירה כסף בין חשבונות באופן אטומי (אם המשיכה מצליחה אך ההפקדה נכשלת, יש לבטל את המשיכה)GetBalance(accountID string) (float64, error): מחזירה את יתרת החשבון
החזר שגיאות מתאימות עבור חשבונות שאינם קיימים.
errors.go: הגדר שגיאות sentinel עבור הפעולות הבנקאיות שלך:ErrAccountNotFound: כאשר מזהה חשבון אינו קייםErrInsufficientFunds: כאשר סכום המשיכה עולה על היתרה הזמינהErrInvalidAmount: כאשר סכום ההפקדה או המשיכה הוא אפס או שליליErrMinBalanceRequired: כאשר משיכה מחשבון חיסכון תפר את דרישת יתרת המינימום
main.go: צור בנק ועבד פעולות על סמך הקלט.קרא את מספר החשבונות שיש ליצור. עבור כל חשבון, קרא את סוג החשבון (
checkingאוsavings), את המזהה, את שם בעל החשבון ואת היתרה הראשונית. צור את סוג החשבון המתאים והוסף אותו לבנק.לאחר מכן, קרא את מספר הפעולות. כל פעולה היא אחת מהבאות:
deposit [accountID] [amount]withdraw [accountID] [amount]transfer [fromID] [toID] [amount]balance [accountID]
עבור כל פעולה, הדפס את התוצאה:
OKעבור הפקדות, משיכות או העברות מוצלחות; את היתרה בפורמט של שתי ספרות אחרי הנקודה העשרונית עבור שאילתות יתרה; אוError: [message]במקרה של כשל, תוך שימוש בהודעות של שגיאות ה-sentinel שלך.
הקלט שיסופק יהיה במבנה הבא:
- מספר החשבונות, ולאחר מכן סוג החשבון, המזהה, בעל החשבון והיתרה הראשונית של כל חשבון (4 שורות לכל חשבון)
- מספר הפעולות, ולאחר מכן כל פעולה (שורה אחת לכל פעולה)
לדוגמה, עבור הקלט:
2
checking
ACC001
Alice
500.00
savings
ACC002
Bob
1000.00
6
deposit ACC001 200
balance ACC001
withdraw ACC002 950
withdraw ACC001 800
transfer ACC001 ACC002 100
balance ACC002הפלט שלך צריך להיות:
OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00ועבור הקלט:
1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50הפלט שלך צריך להיות:
OK
0.00
Error: invalid amount
Error: account not foundשים לב כיצד הממשק Transactable מאפשר ל-Bank לעבוד באופן אחיד עם שני סוגי החשבונות, בעוד שכל סוג חשבון אוכף את הכללים שלו. המתודה Transfer מדגימה פעולות אטומיות: אם משהו משתבש באמצע, המערכת נשארת עקבית!
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// קרא את מספר החשבונות
scanner.Scan()
numAccounts, _ := strconv.Atoi(scanner.Text())
// צור בנק חדש
bank := NewBank()
// קרא וצור חשבונות
for i := 0; i < numAccounts; i++ {
scanner.Scan()
accountType := scanner.Text()
scanner.Scan()
id := scanner.Text()
scanner.Scan()
holder := scanner.Text()
scanner.Scan()
balance, _ := strconv.ParseFloat(scanner.Text(), 64)
// TODO: צור את סוג החשבון המתאים לפי accountType
// והוסף אותו לבנק
_ = accountType
_ = id
_ = holder
_ = balance
}
// קרא את מספר הפעולות
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// עבד כל פעולה
for i := 0; i < numOps; i++ {
scanner.Scan()
parts := strings.Fields(scanner.Text())
operation := parts[0]
// TODO: טפל בכל סוג פעולה:
// - "deposit": הפקד לחשבון, הדפס "OK" או שגיאה
// - "withdraw": משוך מהחשבון, הדפס "OK" או שגיאה
// - "transfer": העבר בין חשבונות, הדפס "OK" או שגיאה
// - "balance": קבל והדפס את היתרה בפורמט של 2 ספרות אחרי הנקודה העשרונית
_ = operation
}
}
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין