ABCs as interfaces, Python Protocol for structural subtyping, Design by Contract, and SysML v2 interface def.
Module 5: Composition & Aggregation
Building objects from objects — lifecycle ownership vs shared references, filled/open diamond UML, and SysML v2 part vs ref.
Composition vs Aggregation
Modules 1–4 explored is-a hierarchies. This module explores the has-a relationship: two forms with very different lifecycle semantics.
| Relationship | Meaning & Lifetime |
|---|---|
| Composition ◆ | Strong ownership. Child created inside parent. Destroying the parent destroys the children. Order ◆→ OrderItem. |
| Aggregation ◇ | Shared reference. Child exists independently, may be referenced from many containers simultaneously. Cart ◇→ Book. |
Composition in Practice
Order composes OrderItem and Payment — they have no meaning outside their Order:
class Order:
def __init__(self, order_id, customer):
self.order_id=order_id; self._status="draft"
self._items: list[OrderItem] = [] # composed: created here
self._payment: Payment | None = None
def add_item(self, book, qty=1):
self._items.append(OrderItem(book, qty, book.price))
def place(self, method):
self._payment = Payment("PAY-"+self.order_id, self.total(), method)
self._payment.process(); self._status="placed"
def cancel(self):
if self._payment: self._payment.refund()
self._status="cancelled" # _items and _payment garbage-collectedAggregation in Practice
ShoppingCart and Inventory hold references to Books they do not own. The same Book object is shared between them:
class ShoppingCart:
def __init__(self, cart_id):
self._items: list[Book] = [] # aggregation: external references
def add_item(self, book): self._items.append(book)
def remove_item(self, book): self._items.remove(book) # book still exists
def to_order(self, order_id, customer):
order = Order(order_id, customer)
for book in self._items: order.add_item(book)
self._items.clear() # cart emptied; books alive elsewhere
return order
# Both containers reference the SAME object
assert inventory.get_book("978-1-11") is cart._items[0] # TrueComposition vs Inheritance
| Scenario | Correct relationship |
|---|---|
| "A DigitalBook is a Book" | Inheritance ✔ — clearly true |
| "A ShoppingCart has Books" | Aggregation ✔ — cart is not a kind of Book |
| "An Order has a Payment" | Composition ✔ — payment is part of the order |
| "A logging Customer" | Composition or Decorator ✔ — not a new customer type |
SysML v2 Bridge
// Composition: Order owns its children
part def Order {
part items : OrderItem[1..*]; // owned lifecycle
part payment : Payment; // owned lifecycle
}
// Aggregation: ShoppingCart references shared Books
part def ShoppingCart {
ref items : Book[0..*]; // NOT owned - shared reference
}part x : ChildClassref x : SomeTypepart keyword (owned lifecycle)ref keyword (shared reference)Summary
Strong ownership. Child created and destroyed with owner. Order owns OrderItem and Payment.
Shared reference. Object exists independently, may be shared from many places.
Delete container = destroy children? Yes = composition. No = aggregation.
Favour composition when is-a is not clearly true. Less coupling, easier to change.
Composition = construct internally. Aggregation = accept as parameter.
part = owned lifecycle. ref = shared reference.