This project demonstrates the Composition Design Pattern in Python. It shows how to use composition instead of inheritance to create flexible, maintainable code that adheres to the Open/Closed Principle.
Composition is a design pattern where objects are composed of other objects (called "has-a" relationship) rather than inheriting from classes (called "is-a" relationship). This approach provides greater flexibility and makes code easier to maintain and extend.
| Benefit | Description |
|---|---|
| 🔄 Flexibility | Change behavior at runtime by swapping composed objects |
| 🔗 Loose Coupling | Classes don't depend on inheritance hierarchies |
| ♻️ Reusability | Payment strategies can be used by any order-like class |
| 🧪 Testability | Easy to mock and test with different payment strategies |
| 📦 Open/Closed Principle | Open for extension (new methods), closed for modification |
Composition example/
├── example.py # Main code with composition pattern implementation
└── README.md # Documentation
Concrete payment strategy for cash payments.
| Property | Details |
|---|---|
| Parameters | amount (float) - The payment amount |
| Method | pay() - Returns formatted cash payment details |
Concrete payment strategy for card-based payments with secure masking.
| Property | Details |
|---|---|
| Parameters | amount (float) - The payment amountcard_number (str) - Full card number (masked in output) |
| Method | pay() - Returns payment details with masked card number (last 4 digits only) |
Represents a customer order that uses composition to delegate payment processing.
| Property | Details |
|---|---|
| Parameters | title (str) - Order descriptionpayment_method - Payment strategy object |
| Method | pay() - Processes payment using the injected strategy |
# Create payment strategies
cash_payment = PayByCash(100)
card_payment = PayByCard(200, "1234-5678-9876-5432")
# Create orders with different payment methods
order1 = Order("Order 1", cash_payment)
order2 = Order("Order 2", card_payment)
# Process payments
print(order1.pay()) # Output: Order 'Order 1' paid: 100 using cash.
print(order2.pay()) # Output: Order 'Order 2' paid: 200 using card ending with 5432.1️⃣ PayByCash & PayByCard → Concrete payment strategies (implement pay() method)
↓
2️⃣ Order class → Accepts any payment strategy object (dependency injection)
↓
3️⃣ order.pay() → Delegates to the payment strategy's pay() method
↓
4️⃣ New payments → Can be added without modifying Order class ✅
To add a new payment method (e.g., digital wallet), simply create a new class with a pay() method:
class PayByWallet:
"""Payment strategy for digital wallet transactions."""
def __init__(self, amount, wallet_id):
self.amount = amount
self.wallet_id = wallet_id
def pay(self):
return f"{self.amount} using wallet {self.wallet_id}"
# Use it immediately
wallet_payment = PayByWallet(150, "wallet_123")
order3 = Order("Order 3", wallet_payment)
print(order3.pay())✅ No changes needed to the Order class! This is the power of composition.
| Principle | Explanation |
|---|---|
| 🎭 Strategy Pattern | Each payment method is a different strategy |
| 💉 Dependency Injection | Payment strategy is injected into Order |
| 🦆 Duck Typing | If it has a pay() method, it works with Order |
| 🎯 Single Responsibility | Each class has one reason to change |
python example.pyExpected Output:
Order 'Order 1' paid: 100 using cash.
Order 'Order 2' paid: 200 using card ending with 5432.
- 🐍 Python 3.6 or higher
- No external dependencies
Created as a learning example for the Composition Design Pattern
Open source - feel free to use and modify