نظام بنكي
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 106 من 107.
التحدي
سهللنقم ببناء نظام بنكي يدير الحسابات، المعاملات، والعمليات المالية! ستقوم بإنشاء نظام قوي حيث تكون سلامة البيانات أمراً بالغ الأهمية — يجب أن تتغير أرصدة الحسابات فقط من خلال طرق تم التحقق منها، ويجب التعامل مع الأخطاء مثل السحب الزائد أو المبالغ غير الصالحة بسلاسة.
ستقوم بتنظيم الكود الخاص بك عبر خمسة ملفات:
account.go: قم بتعريف واجهةTransactableمع توقيعات الدوالDeposit(amount float64) errorوWithdraw(amount float64) error. ثم أنشئ نوعين من الحسابات ينفذان هذه الواجهة:حساب
CheckingAccountمع حقول غير مصدرة (unexported) وهيid،holder، وbalance. تسمح الحسابات الجارية بعمليات السحب التي تجعل الرصيد يصل إلى الصفر تماماً ولكن ليس أقل من ذلك.حساب
SavingsAccountمع نفس الحقول غير المصدرة بالإضافة إلى حقلminBalance. يجب أن تحافظ حسابات التوفير على حد أدنى للرصيد قدره 100.0 — ويجب أن تفشل عمليات السحب التي قد تجعل الرصيد ينخفض عن هذا الحد.يحتاج كلا نوعي الحسابات إلى دوال بناء (
NewCheckingAccountوNewSavingsAccount) ودالة جلبBalance() float64. بالنسبة للإيداعات، ارفض المبالغ السالبة أو الصفرية. بالنسبة للسحوبات، ارفض المبالغ السالبة أو الصفرية والمبالغ التي تتجاوز الأموال المتاحة.transaction.go: أنشئ هيكلTransactionلتسجيل نشاط الحساب. لكل معاملةType(سلسلة نصية: "deposit" أو "withdrawal")، وAmount(float64)، وAccountID(string). قم بتضمين دالة بناءNewTransactionودالةString() stringتعيد التنسيق التالي:[Type] $[Amount] on [AccountID]مع تنسيق المبلغ إلى منزلتين عشريتين.bank.go: ابنِ هيكلBankيدير حسابات متعددة ويسجل المعاملات. قم بتخزين الحسابات في خريطة (map) حسب المعرف (ID) واحتفظ بشريحة (slice) من المعاملات. قم بتنفيذ ما يلي: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 errors) لعملياتك البنكية:ErrAccountNotFound— عندما لا يكون معرف الحساب موجوداًErrInsufficientFunds— عندما يتجاوز السحب الرصيد المتاحErrInvalidAmount— عندما يكون مبلغ الإيداع/السحب صفراً أو سالباًErrMinBalanceRequired— عندما يؤدي سحب التوفير إلى انتهاك الحد الأدنى للرصيد
main.go: أنشئ بنكاً وعالج العمليات بناءً على المدخلات.اقرأ عدد الحسابات المراد إنشاؤها. لكل حساب، اقرأ نوع الحساب (
checkingأوsavings)، والمعرف (ID)، واسم صاحب الحساب، والرصيد الأولي. أنشئ نوع الحساب المناسب وأضفه إلى البنك.ثم اقرأ عدد العمليات. كل عملية هي واحدة من:
deposit [accountID] [amount]withdraw [accountID] [amount]transfer [fromID] [toID] [amount]balance [accountID]
لكل عملية، اطبع النتيجة:
OKللإيداعات/السحوبات/التحويلات الناجحة، أو الرصيد منسقاً إلى منزلتين عشريتين لاستعلامات الرصيد، أوError: [message]للفشل باستخدام رسائل الخطأ الثابتة الخاصة بك.
سيتم توفير المدخلات التالية:
- عدد الحسابات، ثم نوع كل حساب، ومعرفه، وصاحبه، ورصيده الأولي (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 للبنك بالعمل مع كلا نوعي الحسابات بشكل موحد، بينما يفرض كل نوع حساب قواعده الخاصة. توضح طريقة 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": الحصول على الرصيد وطباعته منسقاً إلى منزلتين عشريتين
_ = operation
}
}
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool