انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-020-L06

عرض عملي: الحسابات المصرفية v1 من ملف فارغ

  • الدرس 6 من 7
  • 11 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

This is a follow-along lesson. You will build the lab's Account class and a small Main from an empty file, one small step at a time, using everything from Lessons 1 to 5. At the end you will have a working program, and a clear idea of what the lab (BEG-020-LAB1) expects from you.

The lesson also contains a planned mistake. In step 5 we will write owner = owner; on purpose, see null in the output, and fix it. Seeing a bug appear and disappear is a better lesson than being told about it.

Why this matters: a bank account with an owner, a number, and a balance, with deposit and withdraw rules, is the smallest realistic example of a backend domain class. The same shape repeats for orders, users, and products.

This lesson builds on all of Lessons 1 to 5. Menus differ between IDEs and operating systems, so we describe actions generically ("create a new file") and show the command line where useful.

Learning Objectives

By the end of this lesson you will be able to:

  • Build a class from an empty file in the order: fields, constructors, methods.
  • Write a full constructor and an overloaded one that forwards with this(...).
  • Write deposit and withdraw methods that protect the balance.
  • Keep main to wiring only: create objects, call methods, print.
  • Recognise and fix the missing-this bug from its symptom.
  • Check your work against the lab's acceptance criteria.

Written Explanation

The plan

The lab asks for an Account with an owner, a number, and a balance; a full constructor and an overloaded one; deposit and withdraw; several accounts created in main; and a static counter of accounts. The acceptance criteria are: it compiles, it works, and main contains no logic beyond wiring. "Wiring" means creating objects, calling their methods, and printing. Any rule about money, such as "you cannot withdraw more than you have", belongs in Account, not in main.

We build in this order, compiling after each step:

  1. Fields.
  2. The full constructor.
  3. The overloaded constructor.
  4. deposit and withdraw.
  5. A planned mistake and its fix.
  6. main as wiring.

Working in small steps matters. If the code stops compiling after a change, you know that the last few lines caused it.

Step 1: the fields

Create a new file named Account.java. (In an IDE, use the "new class" action and type the name; on the command line, create the file in an empty folder.) Write:

java
public class Account {
    String owner;
    String number;
    double balance;
}

The file name matches the class name, and the fields sit outside any method. Compile it (javac Account.java on the command line, or your IDE's build action). Nothing runs yet, but the compiler accepts it.

Step 2: the full constructor

Add the constructor that sets all three fields:

java
Account(String owner, String number, double balance) {
    this.owner = owner;
    this.number = number;
    this.balance = balance;
}

Every parameter has the same name as its field, and every assignment starts with this.. This is the pattern from Lessons 2 and 3.

Step 3: the overloaded constructor

A new account often starts empty. Add a second constructor that forwards to the first:

java
Account(String owner, String number) {
    this(owner, number, 0.0);
}

No assignments are repeated. Only one constructor, the full one, sets fields.

Step 4: deposit and withdraw

The methods hold the rules:

java
void deposit(double amount) {
    if (amount > 0) {
        balance += amount;
    }
}

boolean withdraw(double amount) {
    if (amount <= 0 || amount > balance) {
        return false;
    }
    balance -= amount;
    return true;
}

deposit ignores zero and negative amounts. withdraw refuses nonsense and overdrafts, changes the balance only when it can, and tells the caller whether it worked. Add describe() from Lesson 3, which returns owner + " (" + number + "): " + balance, so that main has something to print.

Step 5: the planned mistake

Now suppose that, while editing the full constructor, you slip and write:

java
Account(String owner, String number, double balance) {
    owner = owner;                 // mistake: the parameter is assigned to itself
    this.number = number;
    this.balance = balance;
}

It compiles. When you run the wiring from step 6, Ana's line shows:

text
null (A-1): 150.0

The owner has become null, while the number and balance, which kept this., are fine. This is exactly the symptom from Lesson 3: one field is at its default although a value was passed in. Compare the three assignments: only the one without this. is wrong. Restore this.owner = owner;, run again, and the owner returns.

A tip for finding this bug fast: when a single field is wrong and the others are right, compare its assignment line with the lines that work.

Step 6: main as wiring

Create a second file, Main.java:

java
public class Main {
    public static void main(String[] args) {
        Account ana = new Account("Ana", "A-1", 100);
        Account omar = new Account("Omar", "A-2");
        ana.deposit(50);
        omar.deposit(20);
        boolean ok = ana.withdraw(500);
        System.out.println(ana.describe());
        System.out.println(omar.describe());
        System.out.println("Second withdrawal ok? " + ok);
    }
}

Look at what main does: create two accounts, call methods, print. It has no if, no arithmetic on balances, and no rules. When a requirement changes, only Account changes.

Compile and run both files together (javac Account.java Main.java, then java Main, or use your IDE's run action on Main). The output on JDK 21:

text
Ana (A-1): 150.0
Omar (A-2): 20.0
Second withdrawal ok? false

Ana started with 100 and received 50. Her attempt to withdraw 500 was refused, so her balance stayed at 150. Omar started with 0 through the overloaded constructor and received 20.

Adding the counter

The lab also asks for a static counter of accounts created. Lesson 5 shows how: add static int count = 0; to Account and count++; to the full constructor, so the overloaded constructor counts too through this(...). Then a final line in main, System.out.println("Accounts created: " + Account.count);, prints:

text
Accounts created: 2

Because the short constructor forwards to the full one, the counting line exists in exactly one place. If you had copied the assignments into both constructors, you would have to remember to count in both.

Examples

Example 1: the finished Account (simple)

java
public class Account {
    static int count = 0;

    String owner;
    String number;
    double balance;

    Account(String owner, String number, double balance) {
        this.owner = owner;
        this.number = number;
        this.balance = balance;
        count++;
    }

    Account(String owner, String number) {
        this(owner, number, 0.0);
    }

    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    boolean withdraw(double amount) {
        if (amount <= 0 || amount > balance) {
            return false;
        }
        balance -= amount;
        return true;
    }

    String describe() {
        return owner + " (" + number + "): " + balance;
    }
}

This is the whole class: one static field, three instance fields, two constructors, three methods. You can compare your own version with it, line by line.

Example 2: a method that talks to another account (practical)

A transfer touches two accounts. It still belongs in Account, because it is a rule about money. It can reuse withdraw and deposit, which keeps the checks in one place:

java
boolean transferTo(Account other, double amount) {
    if (this.withdraw(amount)) {
        other.deposit(amount);
        return true;
    }
    return false;
}
java
Account a = new Account("Ana", "A-1", 100);
Account b = new Account("Omar", "A-2");
System.out.println(a.transferTo(b, 30) + " " + a.describe() + " " + b.describe());
System.out.println(a.transferTo(b, 500) + " " + a.balance + " " + b.balance);

Output:

text
true Ana (A-1): 70.0 Omar (A-2): 30.0
false 70.0 30.0

The first transfer works; the second asks for more than Ana has, so withdraw refuses and nothing moves. this is the sending account, other the receiving one, and main has no logic.

Example 3: a main with too much logic (realistic)

Compare the following main, which a beginner might write:

java
if (ana.balance >= 500) {
    ana.balance = ana.balance - 500;
} else {
    System.out.println("Not enough");
}

It works today, but the rule "no overdraft" now lives in main. A second program that uses Account has to copy it, and one forgotten check puts an account into a negative balance. The corrected form is a single call, ana.withdraw(500), plus a print on false. This is the acceptance criterion "no logic in main beyond wiring" in practice.

Backend Insight

A backend application is layered, and each layer has a narrow job. The outermost layer, such as a controller that receives an HTTP request, only translates the request into calls: it creates or finds objects and calls methods, just as main does here. The rules live in the domain classes like Account, and the same withdraw rule applies whether the request comes from a web page, a phone app, or a nightly job.

Later modules will move main's job to frameworks, but the discipline is the same one you practised today: thin edges, rules inside the objects. When you review other people's code, this is one of the quickest quality checks: is the business rule in the class where the data lives, or scattered around callers?

Common Mistakes

Mistake 1: Putting rules in main

If main checks balances, you have a procedural program with objects as storage. Move the check into the method of the class that owns the data.

Mistake 2: Missing this. in the constructor

The classic: owner = owner;. Symptom: one field at its default. Compare the failing assignment with a working one.

Mistake 3: Repeating assignments across constructors

Copying all three assignments into the overloaded constructor works, but any later change has to be made twice. Use this(...).

Mistake 4: Forgetting to compile both files

If Main fails with "cannot find symbol" for Account, the compiler did not see Account.java. Compile both files together, or let the IDE build the whole project.

Mistake 5: Using the result of withdraw as if it always succeeded

Ignoring the boolean means the program reports success when nothing happened. Use the result, even if only to print a message.

Exercises

Try each exercise yourself before opening the solutions. Build on your own Account from this lesson.

Level A: Foundation

A1. Why does the overloaded constructor start with this(owner, number, 0.0); instead of repeating the three assignments?

أظهر الحل A1

A1. So that only the full constructor sets fields. If the rules for setting fields change, there is one place to edit, and no risk that two constructors drift apart.

A2. In the finished program, why does Account.count print 2 after two new calls, even though one of the constructors never mentions count?

أظهر الحل A2

A2. Because the short constructor calls the full one through this(...), and the full one executes count++. Every new ends up there.

A3. Which file contains the rule that an account cannot be overdrawn: Account.java or Main.java? Why?

أظهر الحل A3

A3. Account.java. The class owns the balance, so it enforces the rule; any caller then gets the same behavior and cannot forget the check.

Level B: Application

B1. Add a method boolean isEmpty() that returns true when the balance is 0. Use it in main to print whether Omar's new account is empty.

أظهر الحل B1

B1.

java
boolean isEmpty() {
    return balance == 0;
}
java
System.out.println(omar.isEmpty());   // false after depositing 20; true before

B2. Your teammate's output shows Ana (null): 150.0 for an account created with new Account("Ana", "A-1", 100). Which line do you look for first, and how would you fix it?

أظهر الحل B2

B2. The number field was not assigned: look at the constructor line for it, which probably reads number = number; without this.. Fix: this.number = number;. (Also check the call order of arguments: a swapped call would show a wrong, but not null, value.)

B3. In main, create three accounts, deposit different amounts into each, withdraw too much from one of them, and print all three descriptions. Check that main contains no if.

أظهر الحل B3

B3.

java
Account a = new Account("Ana", "A-1", 100);
Account b = new Account("Omar", "A-2");
Account c = new Account("Sara", "A-3", 7);
a.deposit(10);
b.deposit(20);
c.deposit(30);
c.withdraw(1000);
System.out.println(a.describe());
System.out.println(b.describe());
System.out.println(c.describe());

It prints balances 110.0, 20.0, and 37.0; the oversized withdrawal is refused inside withdraw.

Level C: Challenge

C1. Add transferTo(Account other, double amount) as in Example 2. Then show that a refused transfer leaves both balances unchanged.

أظهر الحل C1

C1. See Example 2. After the refused second call, a.balance and b.balance are still 70.0 and 30.0 because withdraw returned false before deposit ran.

C2. Extend the program with a static method Account.isValidNumber(String number) that returns true when the number starts with "A-", and make the full constructor throw IllegalArgumentException for an invalid number. Explain why the method is static but the check itself runs in the constructor.

أظهر الحل C2

C2.

java
static boolean isValidNumber(String number) {
    return number != null && number.startsWith("A-");
}

and, at the top of the full constructor:

java
if (!isValidNumber(number)) {
    throw new IllegalArgumentException("invalid account number: " + number);
}

The check needs no particular account, only the string, so it can be static and used even before an account exists. The decision to reject is made when an object is being created, so the constructor calls it. (Exceptions are a preview here; a later module teaches them.)

Mini Challenge

Do the lab's core in your own words, without looking at Example 1:

  1. Write Account with owner, number, balance, a full constructor, an overloaded constructor, deposit, withdraw, and describe.
  2. Write Main that creates at least three accounts, moves money between them with the methods, and prints the result.
  3. Add the static counter and print it at the end.
  4. Re-read main and delete any line that is not creating, calling, or printing.

Check your work: it compiles; every printed value matches what you calculated by hand; main has no business rules.

Summary & Next Lesson

Key takeaways

  • Build a class in small steps and compile after each: fields, constructors, methods, then main.
  • The full constructor sets the fields; short constructors forward to it with this(...).
  • Rules (no overdraft, positive deposits) belong in the class that owns the data; main only wires.
  • A single field at its default value, while the others are right, usually means a missing this..
  • The static counter increments in the full constructor, so every new is counted once.

Next lesson

The next lesson, "Demo: static vs instance, seen in the debugger", uses a debugger to show where instance data and static data live, and what happens when balance is made static by mistake.

Self-check

  1. What are the lab's acceptance criteria?
  2. Which constructor does the real work, and how do the others use it?
  3. Which symptom points to a missing this.?
  4. Where should the rule "no overdraft" live?
  5. What may main contain in this lab?
أظهر الإجابات
  1. It compiles, it works, and main has no logic beyond wiring.
  2. The full constructor; the others call it with this(...).
  3. One field is at its default value (null, 0) although a value was passed, while the others are correct.
  4. In Account, inside withdraw.
  5. Creating objects, calling their methods, and printing.

References

  • Oracle, The Java Tutorials, Providing Constructors for Your Classes. Supports: constructors use the class name and have no return type, and overloading by parameter list.
  • Oracle, The Java Tutorials, Using the this Keyword. Supports: this.field = parameter to avoid shadowing, and this(...) as the first line of a constructor.
  • All program output above was produced by compiling and running the code on JDK 21 (javac 21.0.10). The layering comment in Backend Insight is general engineering practice, not tied to a specific source.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.