Advanced OOP Concepts
1. Composition
Composition involves constructing complex objects from simpler ones. It provides more flexibility than inheritance by allowing you to combine objects in various ways.
Theory and Basics
Composition is a fundamental concept in object-oriented programming (OOP) where a class is composed of one or more objects of other classes. This allows for building complex types by combining objects, giving more flexibility compared to inheritance, where classes are derived from other classes.
Key Concepts
- Reusability: By composing objects, we can reuse existing classes without modifying them.
- Flexibility: Composition allows you to change the behavior of a class at runtime by changing the composed objects.
- Encapsulation: It helps in encapsulating the functionality by delegating responsibilities to the composed objects.
Composition vs. Inheritance
- Inheritance: Inheritance represents an "is-a" relationship. For example, a BankAccount is a type of FinancialAccount.
- Composition: Composition represents a "has-a" relationship. For example, a BankAccount has a Customer.
Python Implementation
In Python, composition is achieved by including instances of other classes as attributes of a class.
Bank Account Example
Let's illustrate composition 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):
if not isinstance(customer, Customer):
raise ValueError("customer must be an instance of the Customer class")
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 Composite 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, associates it with a Customer object, and sets the initial balance to zero. It also ensures that the customer parameter is an instance of the Customer class.
- 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 Composite 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 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 Composition
- Modularity: Each component can be developed and tested independently.
- Maintainability: Changes in one component class do not affect others.
- Flexibility: Components can be easily replaced or extended without modifying the composite class.
Conclusion
Composition is a powerful technique in OOP that promotes reusability, flexibility, and maintainability by building complex objects from simpler ones. In the bank account example, composition allows a BankAccount to include a Customer object, encapsulating the relationship between them. This approach adheres to good OOP principles, promoting modularity and making the code easier to maintain and extend.