الاعتمادية (Dependency)
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
The last lesson covered relationships where one object keeps another in a field. Many classes connect in a much lighter way: they use another class for a moment and then let go. That is a dependency.
In the family of contacts picture, a dependency is the plumber. You do not keep him in the family; you call him when a pipe bursts, and if he changes his phone number, you have a problem on that day. In code, if the class you use changes, the class that uses it may break.
Why this matters: the more classes a class depends on, the more reasons it has to change. Most maintenance pain in real projects comes from dependencies nobody noticed. This lesson teaches you to spot them, keep them light, and point them at interfaces.
This lesson builds on the previous two lessons of this module and on interfaces from the module before it.
Learning Objectives
By the end of this lesson you will be able to:
- Define a dependency and say how it differs from an association.
- Recognise dependencies created through parameters, local variables, and return types.
- Explain what can break when a class you depend on changes.
- Reduce a dependency by depending on an interface instead of a concrete class.
- Decide when a dependency deserves to be drawn in a diagram.
Written Explanation
What a dependency is
Class A depends on class B when A needs B to compile or to do its job, but does not keep a reference to it as part of its state. Typical forms:
| Form | Example |
|---|---|
| Parameter | void print(Report r) |
| Local variable | Printer p = new Printer(); inside a method |
| Return type | Report build() |
There is no field. As soon as A stores B in a field, the link becomes an association, because A now remembers B.
A useful test: does the object still know about B after the method returns? If not, it is a dependency.
Why dependencies matter
A dependency is a promise that B will keep offering what A uses. If B renames a method, changes a parameter, or disappears, A no longer compiles (or, worse, behaves differently). Example 3 shows the compiler error.
So the lesson is not "avoid dependencies": any useful program has them. The lesson is to notice them and keep them few and small.
Depend on interfaces
If Bank.sendStatement takes an EmailNotifier, the bank is tied to email. If it takes a Notifier interface, it only depends on the contract: a send(String) method. Any class implementing Notifier works: email, SMS, a test double. This is "programming to an interface" from the previous module, seen as a way to loosen dependencies. The bank now depends on a small stable contract rather than on a specific class that may change often.
Dependency, association, composition: which is which?
| Question | If yes |
|---|---|
Is B a field of A? | Association or stronger |
Does A create B and own it for life? | Composition |
Is B only a parameter, local, or return type? | Dependency |
The three live on a ladder of strength: dependency (weakest, briefest), association, aggregation, composition (strongest).
Reading a dependency from a class
When you open an unfamiliar class, scan three places for other class names: the method signatures (parameters and return types), the bodies (anything created with new or declared as a local), and the import lines at the top. Names that appear only there are dependencies. Names that appear in field declarations are associations. A short list of "what this class knows about" is the first thing to write down before you change it, because every name on the list is something that can break your code or that your change can break.
Static calls count too. Math.max(a, b) makes a class depend on Math. Depending on standard classes is harmless because they hardly ever change, but depending on your own, fast-changing classes is where the cost lies.
Dependencies in diagrams
Not every dependency belongs in a diagram. Drawing every parameter makes it unreadable. Draw a dependency when it explains the design, for example when a class Bank is deliberately tied to a Notifier contract. Lesson 4 gives the notation: a dashed arrow.
Examples
Example 1: a parameter dependency (simple)
class Report {
String render() { return "Report: 3 transactions"; }
}
class Printer {
void print(Report r) { // dependency: parameter, no field
System.out.println(r.render());
}
}Printer has no Report field. It uses a report for one call. If Report.render() were renamed, Printer would stop compiling. That is the dependency.
Example 2: a local-variable dependency (practical)
class Bank {
void printReport(Report report) {
Printer printer = new Printer(); // local variable: still only a dependency
printer.print(report);
}
}Bank creates a Printer inside the method and drops it when the method ends. Bank depends on both Printer and Report, and has an association with neither.
Example 3: depending on an interface (realistic)
interface Notifier {
void send(String message);
}
class EmailNotifier implements Notifier {
public void send(String message) { System.out.println("EMAIL: " + message); }
}
class SmsNotifier implements Notifier {
public void send(String message) { System.out.println("SMS: " + message); }
}
class Bank {
void sendStatement(Account account, Notifier notifier) { // depends on Notifier
notifier.send("Your balance is " + account.getBalance());
}
}Calling it in main with the same account and two notifiers:
bank.sendStatement(acc, new EmailNotifier());
bank.sendStatement(acc, new SmsNotifier());Output (JDK 21):
EMAIL: Your balance is 70.0
SMS: Your balance is 70.0Bank was not changed to support SMS. Now see what a change to a used class costs. If we rename Report.render() to renderText() and do not update Printer, the compiler reports:
Main.java:7: error: cannot find symbol
System.out.println(r.render());
^
symbol: method render()
location: variable r of type Report
1 errorBecause the dependency is explicit, the compiler finds the broken use for us. Dependencies on a small, stable interface rarely break this way.
Backend Insight
In a backend service, a class that sends emails, one that stores data, and one that charges cards are all collaborators. Services typically take their collaborators as parameters or constructor arguments, so tests can substitute fakes. A class that does new EmailNotifier() deep inside its methods is tied to email forever and cannot be tested without sending real mail.
Frameworks you will meet later spend a lot of effort managing dependencies for you. They are easier to understand once you see that each one is simply "class A uses class B", and that the arrow should point at a stable interface.
A practical habit for code review: when you see a new class name in an import line or in a method signature, ask "can this be an interface instead, and does it need to be here at all?" Reviews that catch an unnecessary dependency early save much more work than the same fix after ten other classes have started to rely on it.
Dependencies also explain a design choice you will see often: a class receives what it needs rather than building it. Bank.sendStatement(account, notifier) is easy to test, because a test can pass a fake Notifier that records messages instead of sending them. A version that calls new EmailNotifier() inside the method would have no such seam.
Common Mistakes
Mistake 1: Storing a collaborator in a field out of habit
If a class only needs another object for one call, pass it as a parameter. A field makes the link permanent and the class harder to reuse.
Mistake 2: Depending on a concrete class when an interface exists
A parameter of type EmailNotifier locks the method to email. Prefer Notifier, unless the method truly needs something only EmailNotifier offers.
Mistake 3: Depending on many classes at once
A class that uses a dozen others is touched by changes in all of them. This is often a sign that the class does too much. Split it.
Mistake 4: Drawing every dependency
A diagram with every parameter type drawn as an arrow cannot be read. Show the dependencies that carry design meaning.
Mistake 5: Calling every link a dependency
If the object keeps the reference in a field and uses it across several calls, it is an association. Use the strongest name that is true.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. Does a dependency require a field? Explain in one sentence.
أظهر الحل A1
A1. No. A parameter, a local variable, or a return type is enough for a dependency. A field turns it into an association.
A2. Say whether each is a dependency or a stronger link: (a) class Car { private Engine engine; }; (b) void drive(Route route); (c) a local variable Logger log = new Logger(); inside a method.
أظهر الحل A2
A2. (a) Association (a field). (b) Dependency (parameter). (c) Dependency (local variable).
A3. Put these in order from weakest to strongest: composition, dependency, association.
أظهر الحل A3
A3. Dependency, association, composition.
Level B: Application
B1. Write an interface Notifier with void send(String message), one implementation, and a method Bank.sendStatement(Account, Notifier). Run it with two different notifiers.
أظهر الحل B1
B1. We compiled and ran the code from Example 3. The output is the same two lines:
EMAIL: Your balance is 70.0
SMS: Your balance is 70.0B2. You rename Report.render() to renderText() but do not update Printer. What happens and when do you find out? Does that happen with a dependency, an association, or both?
أظهر الحل B2
B2. Printer fails to compile with cannot find symbol at the call to r.render(), as in Example 3, and you learn of it at compile time, before running. This happens for both: any code that calls a method of another class, whether through a parameter or a field, breaks when that method disappears. What differs is not the compiler error but how widely the class is tied to the other: an association is usually used by many methods, so more code needs fixing.
B3. A method has the signature void alert(EmailNotifier n). Change it so that SMS can be used too, and state what you gain.
أظهر الحل B3
B3. Change the parameter to the interface: void alert(Notifier n). You gain the ability to pass any Notifier, including a new channel added later, without changing alert.
Level C: Challenge
C1. OrderService has these uses: a Clock read once in placeOrder; a PaymentGateway used in every method; a Receipt returned from placeOrder. Classify each as dependency or association, and say which ones you would make fields.
أظهر الحل C1
C1. Clock: dependency (used once, locally); keep it as a parameter or local. PaymentGateway: association, because every method needs it, so it should be a field set once. Receipt: dependency through the return type; the service does not keep it.
C2. A teammate says: "Let's make every dependency a field so we never pass anything around." Give two reasons this hurts the design.
أظهر الحل C2
C2. First, fields turn short-lived uses into long-lived state, so the class carries references it does not need and is harder to reuse. Second, shared mutable fields make code harder to follow and test: two callers may overwrite what the other set. (A field is right only for what the object must remember.)
Mini Challenge
Pick one class you wrote earlier in the course (for example the bank Account). List every other class it mentions in a parameter, a local variable, or a return type, and every class it holds in a field.
- Mark each as dependency or association.
- Pick one dependency on a concrete class and decide whether an interface would make sense.
- Write one sentence on whether you would draw that dependency in a diagram.
Check your work: each field appears in the association list, and no field is listed as a dependency.
Summary & Next Lesson
Key takeaways
- A dependency is a use without a field: a parameter, a local variable, or a return type.
- If the used class changes, the user may break, and the compiler will often tell you where.
- Prefer depending on an interface rather than a concrete class.
- Keep dependencies few. A class with many dependencies often does too much.
- Draw only the dependencies that explain the design.
Next lesson
The next lesson, "UML class diagrams, the useful 20%", gives you the notation for everything so far: boxes, lines, diamonds, a dashed arrow for dependency, and multiplicity labels.
Self-check
- Does a dependency need a field?
- Name three forms a dependency can take.
- Why does depending on
Notifierbeat depending onEmailNotifier? - If a class keeps the collaborator in a field, what is the relationship called?
- Is a class with fifteen dependencies a good sign?
أظهر الإجابات
- No: a parameter, local variable, or return type is enough.
- Parameter, local variable, return type.
- The caller can pass any implementation, and the class depends only on a small contract.
- An association (or something stronger).
- Usually not. It often means the class has too many jobs.
References
- Wikipedia, Class diagram. Supports that UML shows a dependency as a dashed line with an open arrowhead from the dependent class to the one it depends on.
- Java compiler messages and program output in this lesson come from compiling and running the code with JDK 21. The ladder of relationship strength is this course's teaching summary rather than a quoted standard.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.