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

عندما يتوقف الكود الإجرائي عن التوسّع

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

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

Lesson Overview

So far every program you wrote kept its data in variables and arrays and its logic in static methods that receive the data as parameters. That style is called procedural programming. It works well for small programs. This lesson shows, with a bank example, the point where it starts to hurt.

You will build a tiny bank with one account, then grow it to many accounts using parallel arrays, and watch three problems appear: related data is split apart, nothing protects the data, and adding one new field forces changes everywhere. Then you will meet the idea that answers those problems: object-oriented programming (OOP), which groups data and the methods that use it into one unit.

Why this matters: nearly all Java backend code is object-oriented. Understanding the problem OOP solves is what makes the syntax in the next module feel natural instead of arbitrary.

This lesson builds on methods and parameters (BEG-014) and arrays (BEG-015). It uses money in cents stored in an int, as earlier modules recommended.

Learning Objectives

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

  • Describe a procedural solution: data in variables, logic in methods that receive the data.
  • Build a many-account bank with parallel arrays and say how they are linked.
  • Name three problems of that design: split data, no protection, and costly change.
  • Explain, in one sentence, what OOP does differently.
  • Contrast a procedural and an object-oriented way to express the same deposit.

Written Explanation

Procedural: data over here, logic over there

In procedural code the data and the code that works on it are separate things. The data lives in variables. The logic lives in methods. Each method receives the data it needs as parameters and sends a result back or changes what it was given.

For a single account this is perfectly good:

java
public class Single {
    static int deposit(int balance, int amount) {
        return balance + amount;
    }
    public static void main(String[] args) {
        int balance = 10000;          // 100.00 in cents
        balance = deposit(balance, 5000);
        System.out.println("Balance: " + balance);
    }
}

Output:

text
Balance: 15000

One variable, one method, nothing to confuse. Now the bank gets customers.

Growing to many accounts: parallel arrays

A bank with 1,000 accounts cannot have 1,000 separate variables. You already know the answer from BEG-015: use arrays. An account has an owner and a balance (and later a number, a currency, and more), so you need one array per property:

java
String[] owners   = {"Ana", "Omar"};
int[]    balances = {10000, 5000};

These are parallel arrays: index 0 in both arrays describes Ana's account, index 1 describes Omar's. The link between them exists only in your head and in your code's discipline. Java does not know that owners[1] and balances[1] belong together.

The methods now need the arrays and an index to say which account:

java
static void deposit(int[] balances, int i, int amount) {
    balances[i] += amount;
}

Problem 1: related data is split apart

Everything that describes one account is scattered over several arrays. To show one account you must pass several arrays and an index. To add an account you must add an element to every array, and keep the positions in step. Nothing in the program says "these values form one thing".

The danger is silent. Suppose you sort the owners alphabetically but forget to move the balances in the same way. Both arrays stay valid, the program compiles, and every balance now belongs to the wrong person (see Example 2).

Problem 2: nothing protects the data

balances is an ordinary array. Any method, and any line in main, can write any value anywhere:

java
balances[0] = -99900;   // legal; no rule is checked

The deposit method may be careful, but nothing forces other code to go through it. In a large program written by many people, "everyone will remember to call the right method" is not a strategy. A rule such as "a balance never becomes negative through a deposit" is only as strong as the weakest line that ignores it.

Problem 3: change spreads everywhere

Next month the bank adds a new property: a currency for each account. In the array design that means a new array currencies, plus a change to every method that must now carry the extra array and index, plus a change to every place that adds or removes an account. With ten methods, that is ten edits and ten chances to forget one. The compiler will not always tell you: a forgotten update in a sorting routine compiles fine.

The object-oriented answer

Object-oriented programming organizes a program around objects. An object bundles the data that describes one thing (its state) together with the methods that work on that data (its behavior). Instead of three arrays and a loose deposit method, the program has account objects, and each one knows its own owner and balance and can deposit into itself.

Compare how the same deposit reads:

StyleA deposit of 25.00 into the second account
Proceduraldeposit(balances, 1, 2500);
Object-orientedomar.deposit(2500);

In the procedural line the caller must know which array, which index, and that the arrays line up. In the object-oriented line the caller names the account and says what to do; the account takes care of its own balance. The Java tutorial describes this bundling as a main benefit of objects: the object's code can be written and maintained on its own, and details can be hidden from the outside. (Hiding details has its own module later.)

Here is the honest summary. OOP does not make a program faster, and it does not make a small program better. It makes a growing program easier to keep correct, because related data sits in one place, the rules sit next to the data they protect, and a new property is added in one place. You will write the Java syntax for it in the next module; for now, learn to see the problem it solves.

Examples

Example 1: one account, procedural (simple)

The Single program in the explanation above is the baseline. Three observations:

  1. deposit does not own the balance; it just returns a new number, and main must store it.
  2. The program is small, so no problem is visible yet.
  3. If you want a second account you must copy the variable and repeat the code, or move to arrays.

Procedural code is not "bad". For a 20-line program it is the right tool.

Example 2: parallel arrays and what goes wrong (practical)

java
public class Parallel {
    static void deposit(int[] balances, int i, int amount) {
        balances[i] += amount;
    }
    static void show(String[] owners, int[] balances, int i) {
        System.out.println(owners[i] + ": " + balances[i]);
    }
    public static void main(String[] args) {
        String[] owners = {"Ana", "Omar"};
        int[] balances = {10000, 5000};
        deposit(balances, 1, 2500);
        show(owners, balances, 1);
        balances[0] = -99900;          // nothing stops this
        show(owners, balances, 0);
        deposit(balances, 7, 100);     // wrong index
    }
}

Real output:

text
Omar: 7500
Ana: -99900
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 2
	at Parallel.deposit(Parallel.java:3)
	at Parallel.main(Parallel.java:15)

Reading it: the legal deposit worked. The direct write balances[0] = -99900 was accepted silently and Ana now has a negative balance that no rule approved. The last call used a wrong index and failed at run time (the stack trace is the kind you will learn to read in BEG-018).

Now the desynchronization problem. Here the owners are swapped to sort them, but the balances are forgotten:

java
String[] owners = {"Ana", "Omar"};
int[] balances = {10000, 5000};
String tmp = owners[0]; owners[0] = owners[1]; owners[1] = tmp;
System.out.println(owners[0] + ": " + balances[0]);
text
Omar: 10000

Omar was given Ana's money. No error, no warning.

Example 3: adding currency (realistic)

Suppose the bank has these three methods: deposit, show, and transfer. A manager asks: "every account needs a currency, and show must print it."

Procedural changes needed:

  1. Add String[] currencies = {"SAR", "SAR"}; in main.
  2. Change show to receive currencies too, and every call to show.
  3. Change transfer so it refuses to move money between accounts with different currencies, so it also receives currencies, and every call to it.
  4. Find every place that adds or removes an account (and the sorting code) and update the new array there too.

Count the edits: the array, two signatures, every call site, every add/remove location. Miss one and the program still compiles.

Object-oriented changes needed: add one currency property to the account's definition, and update the code that prints it. Methods that already receive an account get the new property for free, because it travels inside the account. You will see this fully in the module's demo lesson.

Backend Insight

A backend service handles many "things" at once: thousands of customers, orders, and payments. Each thing has several properties that must stay consistent (an order's status, its total, its customer). Parallel arrays of those properties would be unmanageable, and the failure would be silent data corruption, which is the worst kind of bug in a system that holds people's money.

Real backends therefore model each thing as an object: a Customer, an Order, a Payment. The framework and database tools you will meet later are built on that assumption. When you read a backend project, most of the files are definitions of such things and of the methods that act on them. This module is the start of domain modeling: deciding which things exist in the problem and what each one knows and does.

Common Mistakes

Mistake 1: Thinking OOP is simply "newer and better"

  • Incorrect: "Procedural code is wrong; always use objects."
  • Correct: "Procedural code is fine for small, short programs. OOP pays off when many related pieces of data must stay consistent as the program grows."

Mistake 2: Thinking parallel arrays are protected by the order

  • Incorrect: "Index 1 is always Omar in every array, so it is safe."
  • Correct: "The link exists only by convention. Any code that reorders, inserts into, or removes from one array and not the others breaks it silently."

Mistake 3: Blaming the method instead of the design

If deposit is correct but a balance is still wrong, the cause may be some other line writing directly into the array. The design allows it. A good design makes the wrong thing hard to do, not merely discouraged.

Mistake 4: Believing OOP removes methods

OOP does not remove methods; it moves them. The deposit logic is still a method, but it now lives with the data it works on.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. In your own words, what is the difference between data and logic in procedural code?

أظهر الحل A1

A1. Data is the values the program stores (variables, arrays). Logic is the instructions that work on them (methods). In procedural code they are kept apart and connected only by passing the data as parameters.

A2. A school stores 300 students as String[] names, int[] ages, double[] gpas. Name two ways this can go wrong.

أظهر الحل A2

A2. Any two: the three arrays can get out of step (sorting one but not the others); any code can write any value (an age of -5); adding a property means a new array and changes to every method that handles students; a wrong index reads the wrong student's data without an error if the index is valid for a different student.

A3. Fill in the blank: OOP groups together the ____ that describes one thing and the ____ that use it.

أظهر الحل A3

A3. Data (state) and methods (behavior).

Level B: Application

B1. Here is a procedural library with parallel arrays:

java
String[] titles = {"Dune", "Emma"};
boolean[] borrowed = {false, true};
static void borrow(boolean[] borrowed, int i) { borrowed[i] = true; }

Write the line that borrows "Dune". Then write one line from anywhere in main that breaks the library's rules in a way borrow could never prevent (hint: think of an invalid state).

أظهر الحل B1

B1. borrow(borrowed, 0); An example of a rule-breaking line: borrowed[5] = true; would crash with an out-of-bounds error, but borrowed[1] = false; silently marks a book returned without going through any return rule, and titles[0] = null; leaves a borrowed flag with no valid title. Any line that changes the arrays without going through the intended method is an acceptable answer.

B2. The library adds a String[] authors array. List which parts of the program must change.

أظهر الحل B2

B2. Add the array; every method that shows or needs the author must receive the array; every call to those methods; every place that adds, removes, or sorts books must update the new array too.

B3. Write, in words, how "return a book" looks procedurally and object-oriented, using the same style as the deposit table.

أظهر الحل B3

B3. Procedural: giveBack(borrowed, 0); The caller must know which array and index. Object-oriented: dune.giveBack(); The book object changes its own state.

Level C: Challenge

C1. Write a program with parallel arrays for three products (names and prices in cents). Add a method static int total(int[] prices). Then add a second array int[] stock and change the program to print "name: price x stock". List every method signature you had to change.

أظهر الحل C1

C1. Example solution:

java
static int total(int[] prices) {
    int sum = 0;
    for (int p : prices) sum += p;
    return sum;
}
static void show(String[] names, int[] prices, int[] stock, int i) {
    System.out.println(names[i] + ": " + prices[i] + " x " + stock[i]);
}

The show signature had to change (it gained stock), and every call to it. If you also had add/remove or sort code, that changes too. Compiling and running confirms the change; the point is how many places the new property touched.

C2. Argue for or against: "For a 30-line program that converts temperatures, OOP is worth it." Use the three problems from this lesson in your answer.

أظهر الحل C2

C2. A good answer is "against". With one or two variables there is nothing to keep consistent (no split data), the program is too small for data protection to matter, and a new field touches almost nothing. The three problems appear only as the amount of related data grows.

Mini Challenge

Choose a small real-world collection that you know well: for example songs in a playlist, items on a menu, or players in a team. Describe it as parallel arrays with at least three properties (write the array declarations with two sample entries each).

Then:

  1. Write one line that corrupts the data without any rule noticing.
  2. Write what must change if you add a fourth property.
  3. Write, in one sentence, how an object for one item would look from the caller's side (for example song.play()), without worrying about Java syntax.

Check your work: you named a concrete corruption, you counted the places a new property touches, and your sentence shows the caller talking to one thing instead of several arrays.

Summary & Next Lesson

Key takeaways

  • Procedural code keeps data in variables and arrays and logic in methods that receive the data. It is fine for small programs.
  • With many accounts you need parallel arrays, whose link exists only by convention.
  • Three problems appear as programs grow: related data is split, nothing protects it, and adding a property touches everything.
  • OOP groups the data of one thing with the methods that use it.
  • Backend systems model their domain (customers, orders, payments) as objects.

Next lesson

Next is "Objects: state + behavior". You will learn the vocabulary for what an object is: what it knows (state) and what it does (behavior).

Self-check

  1. Name one problem with parallel arrays for accounts.
  2. Why can balances[0] = -99900; be a problem even if deposit is written correctly?
  3. What must change in a parallel-array program when you add a currency property?
  4. What does OOP group together?
  5. Is procedural code "wrong"? Explain briefly.
أظهر الإجابات
  1. Related data is split across arrays, so it is easy to mix up or corrupt, and nothing links the values except position.
  2. Because nothing forces other code to use deposit: any line can write a value directly, bypassing the rules.
  3. A new array, plus changes to every method, call, and add/remove/sort code that must handle it.
  4. The data of one thing and the methods that use that data.
  5. No. It is a good fit for small programs; its weaknesses appear as related data grows.

References

  • Oracle, The Java Tutorials: What Is an Object?. Supports the description of objects as bundles of state (fields) and behavior (methods), and the benefits of modularity and information hiding.
  • The array behavior, the ArrayIndexOutOfBoundsException message, and all outputs shown were produced by compiling and running the examples with JDK 21 (javac 21.0.10). The claim that OOP is not about speed is a general design statement, not a measurement.

التطبيق

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

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

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

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