Aggregation is a special form of association that represents a "has-a" relationship where the child objects can exist independently of the parent. It models a whole-part relationship but with independent lifecycles. This means that even if the parent object is destroyed, the child objects can continue to exist.
Key Concepts
- Independent Lifecycles: The parent and child objects have independent lifecycles. The child object can exist without the parent object.
- Whole-Part Relationship: Aggregation represents a whole-part relationship, but the parts can exist independently.
- Flexibility and Reusability: Child objects can be shared among different parent objects.
Aggregation vs. Composition
- Composition: Represents a strong ownership where the lifecycle of the composed objects is tightly bound to the lifecycle of the parent object. If the parent object is destroyed, the composed objects are also destroyed.
- Aggregation: Represents a weaker relationship where the child objects can exist independently of the parent object.
Practical Implementation in Python
In Python, aggregation can be represented by creating classes that include references to other classes as attributes. The child objects can be created outside the parent class and then passed to it.
Bank Account Example
Let's illustrate aggregation with a BankAccount and a Customer.
- Define the Customer Class
class Customer:
def __init__(self, name, customer_id):
self.name = name
self.customer_id = customer_id
def __str__(self):
return f"Customer Name: {self.name}, Customer ID: {self.customer_id}"
Define the BankAccount Class
class BankAccount:
def __init__(self, account_number, customer):
self.account_number = account_number
self.customer = customer
self.balance = 0.0
def deposit(self, amount):
if amount <= 0:
print("Deposit amount must be positive")
return
self.balance += amount
print(f"Deposited {amount:.2f}, new balance is {self.balance:.2f}")
def withdraw(self, amount):
if amount <= 0:
print("Withdrawal amount must be positive")
return
if amount > self.balance:
print("Insufficient funds")
return
self.balance -= amount
print(f"Withdrew {amount:.2f}, new balance is {self.balance:.2f}")
def get_balance(self):
return self.balance
def __str__(self):
return f"Account Number: {self.account_number}, Balance: {self.balance:.2f}"
Using the Aggregated Classes
# Create a Customer instance
customer = Customer("John Doe", "CUST123")
# Create a BankAccount instance associated with the customer
account = BankAccount("001", customer)
# Perform operations on the bank account
account.deposit(500)
account.withdraw(200)
account.withdraw(400) # This will trigger an "Insufficient funds" message
# Display account details
print(account)
print(account.customer)
Detailed Explanation
Step-by-Step Breakdown
- Defining the Customer Class:
- The Customer class represents a bank customer with attributes name and customer_id.
- The __str__ method provides a string representation of the customer, which is useful for displaying customer details.
- Defining the BankAccount Class:
- The BankAccount class represents a bank account associated with a Customer.
- The constructor initializes the account number and associates it with a Customer object. The Customer object is passed as a parameter, demonstrating the independent lifecycle of the Customer and BankAccount classes.
- The deposit method increases the account balance by the specified amount, with validation to ensure the amount is positive.
- The withdraw method decreases the account balance by the specified amount if sufficient funds are available, with validation to ensure the amount is positive and that there are enough funds.
- The get_balance method returns the current balance.
- The __str__ method provides a string representation of the bank account, displaying the account number and balance.
- Using the Aggregated Classes:
- An instance of Customer is created with a name and a customer ID.
- An instance of BankAccount is created with an account number and the previously created Customer object. The Customer object exists independently and is not created within the BankAccount class.
- The deposit method is used to add funds to the account, and the withdraw method is used to withdraw funds, demonstrating how the balance changes.
- The account details and customer details are printed using the __str__ methods of both classes.
Advantages of Aggregation
- Independent Lifecycles: Child objects can exist independently of the parent object, allowing for greater flexibility.
- Reuse of Objects: The same child object can be associated with multiple parent objects.
- Flexibility: Aggregated objects can be easily shared, replaced, or extended without affecting the parent class.
Conclusion
Aggregation is a powerful technique in OOP that allows for a flexible and reusable way to build complex objects from simpler ones. In the bank account example, aggregation allows a BankAccount to include a Customer object, demonstrating an independent lifecycle for both. This approach adheres to good OOP principles, promoting modularity, reusability, and flexibility in software design.