Class Relationships
Dependency
Uses-a relationship
Dependency is the most flexible and weakest structural relationship between classes in Object-Oriented Design. It represents a transient, short-term connection where one class utilizes another class (frequently as a method parameter, local variable, or return type) to perform a specific task, without holding any persistent reference to it.
1. Learning Objectives
- Define Dependency and identify its UML dashed arrow representation.
- Distinguish Dependency from Association, Aggregation, and Composition based on field retention and lifetime.
- Implement transient parameters in Java, Python, and C++ class methods.
- Trace stack allocations and method frame registers to understand where dependencies reside in memory.
- Apply the Dependency Inversion Principle (DIP) to decouple service dependency paths.
2. Problem Statement
In a complex software application, classes must interact to execute tasks. However, if a class holds persistent instance fields (Association) for every auxiliary tool, calculator, database driver, or data transfer object it uses, we run into major design issues:
- High Memory Consumption: Storing references as fields keeps them allocated on the heap long after the operation is complete.
- Thread-Safety Violations: Sharing stateful objects like
OrderorPaymentTransactionas instance variables across multi-threaded services causes race conditions. - Rigid Architecture: Hardcoding dependencies limits our ability to swap implementations or test classes in isolation.
We need a way to design classes so they use other objects temporarily during a specific method execution, rather than maintaining permanent links.
3. Real-world Analogy
Think of a Customer and a Credit Card Reader at a store:
- The Customer uses the Card Reader temporarily to pay for groceries.
- The Customer does not carry the card reader around, nor does the card reader belong to the customer's permanent structural properties.
- Once the transaction is processed, the interaction ends. The Customer walks away, and the reader is immediately ready for another customer.
This temporary connection is a Dependency. The Customer class "uses-a" Card Reader temporarily to execute the pay behavior.
4. Theory
Dependency represents a "uses-a" relationship. It is the weakest form of class relationship, indicating that a change in the definition of one class (e.g., class B) may affect another class (e.g., class A) because class A uses class B, even if it doesn't store a reference to it.
Key Characteristics:
- Transient Context: The connection is established only for the duration of a method call or execution block.
- No Field Storage: The dependent class does not define instance variables pointing to the target class.
- UML Representation: Indicated by a dashed arrow line pointing from the dependent class to the class being used.
Common Manifestations of Dependency:
- Method Parameter: The target object is passed as a method argument:
process(Order order). - Local Variable: The target object is instantiated inside the method:
Calculator calc = new Calculator(). - Method Return Type: The target object is returned by a method:
public Order getOrder().
5. Visual Diagrams (UML & Memory structures)
Class Diagram
Dashed arrows point to classes that are used temporarily. PaymentService depends on Order and PaymentGateway.
PaymentGateway
Memory Diagram (Transient Stack Frame)
When processPayment is invoked, references to order and gateway are pushed onto the method's stack frame. When the method finishes execution, the stack frame is popped, and these temporary pointers are discarded.
JVM Stack Frame (Active Call)
- order = @0x70B2
- gateway = @0x90A1
Heap space
amount: 99.99
endpoint: "stripe.api.com"
Object Lifecycle
6. Syntax Explanation
Dependency is declared by referencing another type inside method signatures, local blocks, or return signatures, rather than declaring instance fields:
- Java: Methods accept references, e.g.,
public boolean process(Order order, PaymentGateway gateway). Interfaces are preferred for parameters to keep dependencies decoupled (Dependency Inversion). - Python: Uses method parameters. Type hints, e.g.
def process(self, order: 'Order', gateway: 'PaymentGateway') -> bool:, help document the dependency. - C++: Parameters are passed as references or pointers to prevent unnecessary object slicing or copying:
bool process(const Order& order, PaymentGateway* gateway).
7. Step-by-Step Implementation
- Step 1: Create the
Orderentity representing the data package. - Step 2: Create the
PaymentGatewayinterface (representing the abstraction) and a concrete implementation classStripeGateway. - Step 3: Create the
PaymentServiceclass. Do not define any fields. Declare a methodprocessOrderPaymentthat takesOrderandPaymentGatewayas arguments. - Step 4: Write a verification script: instantiate the objects, call the process method, and verify that
PaymentServiceretains no references to the order or gateway after the method completes.
8. Complete Code (Mini Project)
9. Code Walkthrough
The core design feature of the PaymentService class is its complete lack of instance fields:
- The class does not define member variables for
OrderorPaymentGateway. If it did, it would be an Association instead of a Dependency. - By declaring parameters in the method signature
processOrderPayment(Order order, PaymentGateway gateway), we decouple the lifetime ofPaymentServicefrom the database objects and payment adapters it uses. - In C++, passing arguments as
const Order& orderandPaymentGateway& gatewayavoids copying objects while keeping the runtime dependency light and fast.
10. Execution Flow
- Construction:
PaymentServiceis instantiated. It has zero fields, taking up minimal heap space. - Invocation: The client calls
processOrderPayment(order, gateway). - Stack Allocation: A new stack frame for the method is created. The memory addresses of the order and gateway are pushed onto this frame.
- Execution: The service delegates the payment execution to the gateway.
- Method Pop: The execution returns a boolean. The stack frame is destroyed, and the references are discarded. The service has no remaining connection to either object.
11. Internal Working
During method execution, local references exist only inside the active thread's stack frame.
When paymentService.processOrderPayment(order, gateway) is called, the JVM creates a new stack frame containing slots for local variables (specifically the reference addresses of the arguments).
Once the method returns, its stack frame is popped off. The local reference variables disappear. Since the PaymentService object has no instance fields holding these heap addresses, the garbage collector is free to clean up the order or gateway objects as soon as the client code releases them.
12. Complexity Analysis
- Time Complexity: $O(1)$ to pass references on the stack frame. The execution speed depends entirely on the method's internal logic.
- Space Complexity: $O(1)$ transient stack allocations per call. No heap space is consumed by the calling service.
13. Best Practices
- Depend on abstractions: Always declare method parameters as interface types (e.g.,
PaymentGateway) rather than concrete classes (e.g.,StripeGateway) to keep components decoupled. - Avoid persistent state inside services: Helper classes, services, and utility classes should remain stateless and accept variables only via method arguments.
- Inject dependencies: When a class needs a service, inject it rather than instantiating the concretion internally.
14. Common Mistakes
- Converting temporary variables into fields: Storing transient variables (like
Orderdata packages) as instance fields in service singletons. This causes memory leaks and severe multi-threading bugs. - Concrete Instantiation inside methods: Writing
PaymentGateway gateway = new StripeGateway()inside a method. This tightly couples the service to a concrete gateway, making it impossible to mock the dependency for unit testing. - Circular Dependencies: Class A depending on Class B, and Class B depending on Class A. This makes the codebase rigid and difficult to compile. Resolve circular dependencies by introducing interfaces.
15. Interview Questions
Answer: In Association, one class holds a persistent reference to another class as an instance field (e.g., a permanent link). In Dependency, the link is transient; one class uses another temporarily inside a method scope (e.g., as a parameter or local variable) and does not store it.
Answer: DIP states that high-level modules should not depend on low-level modules; both should depend on abstractions. In code, this means method parameters should be defined as interfaces (e.g.,
PaymentGateway) rather than concrete implementations (e.g., StripeGateway).
Answer: It allows you to pass mock or stub implementations (like
MockPaymentGateway) during testing, letting you test the service's logic in isolation without making real API calls.
16. Practice Exercises
- Easy: Implement a
ReportGeneratorclass that depends on aDateFormatterhelper passed to itsgenerate()method. - Medium: Design a
NotificationServiceclass. Implement asendAlert(User user, MessageTemplate template)method. Ensure the templates are passed transiently. - Hard: Create a thread-safe
FileExporterclass. Expose anexportData(Dataset dataset, DataWriter writer)method. Verify that multiple threads can safely execute exports concurrently using different datasets and writer targets.
17. Challenge Problem
Design a custom Dependency Injection (DI) Container from scratch in Java/Python that scans classes, registers dependencies, and injects them dynamically into class methods at runtime.
18. Summary
- Dependency is the weakest relationship, representing temporary "uses-a" behavior.
- Dependencies exist only during execution scope and are not stored in instance fields.
- In UML, Dependency is represented by a dashed arrow pointing to the utilized class.
- Mocking dependencies allows for clean, isolated unit testing.
19. Cheat Sheet
| Relationship Type | UML Arrow Style | Field Retention | Lifecycle Constraint |
|---|---|---|---|
| Association | Solid line (or arrow) | Yes (Instance Field) | Fully Independent |
| Aggregation | Hollow diamond pointing to whole | Yes (Instance Field) | Independent survival |
| Composition | Filled diamond pointing to whole | Yes (Instance Field) | Part dies with whole |
| Dependency | Dashed arrow pointing to target | No (Transient reference only) | Tied to execution scope |
20. Quiz
1. What does a Dependency relationship indicate?
A) High-level module inherits properties from a base class
B) One class owns and manages the lifecycle of another class
C) One class temporarily uses another class during execution (Correct)
2. Which UML symbol represents Dependency?
A) Solid diamond
B) Dashed line with arrowhead (Correct)
C) Hollow diamond
3. Why is Dependency considered the weakest class relationship?
A) Because it has the lowest execution speed priority
B) Because the dependent class does not store a persistent reference to the target class (Correct)
C) Because it cannot be compiled in C++
4. Which of the following represents a Dependency manifestation?
A) An object passed as a method parameter
B) An object instantiated locally inside a method body
C) All of the above (Correct)
5. Where do local reference variables reside during method execution?
A) Heap Area
B) JVM Stack Frame (Correct)
C) Metaspace Constants Pool
6. What design principle helps reduce coupling when dealing with dependencies?
A) Single Responsibility Principle
B) Dependency Inversion Principle (Correct)
C) Open/Closed Principle
7. What is a common mistake when implementing stateless services?
A) Storing transient parameter variables as instance fields (Correct)
B) Passing arguments by constant reference in C++
C) Using interface arguments
8. How does a Dependency affect unit testing?
A) It makes classes untestable
B) It allows passing mock implementations, letting you test classes in isolation (Correct)
C) It requires establishing database connections
9. What complexity is associated with passing references as parameters?
A) O(1) time and space stack allocation (Correct)
B) O(N) heap allocation overhead
C) O(N log N) recursive sorting
10. What is circular dependency?
A) A class inheriting from itself
B) Class A depending on Class B, and Class B depending on Class A (Correct)
C) A method returning its own class type
21. Next Lesson Preview
In the next module, we will explore Design Principles, starting with the DRY (Don't Repeat Yourself) principle to learn how to identify and extract duplicate code layouts!