Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نظام بنكي

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 106 من 107.

challenge icon

التحدي

سهل

لنقم ببناء نظام بنكي يدير الحسابات، المعاملات، والعمليات المالية! ستقوم بإنشاء نظام قوي حيث تكون سلامة البيانات أمراً بالغ الأهمية — يجب أن تتغير أرصدة الحسابات فقط من خلال طرق تم التحقق منها، ويجب التعامل مع الأخطاء مثل السحب الزائد أو المبالغ غير الصالحة بسلاسة.

ستقوم بتنظيم الكود الخاص بك عبر خمسة ملفات:

  • 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

16التحديات النهائية

منصة تعليم إلكترونينظام بنكينظام شخصيات الألعاب