Composition
Strong ownership - parts cannot exist without the whole.
Composition is a “has-a” relationship where one class contains another, and the contained class cannot exist independently. It represents a strong ownership relationship with tied lifecycles.
What is Composition?
Section titled “What is Composition?”Composition represents:
- “Has-a” relationship (strong ownership)
- Parts cannot exist independently of the whole
- Parts belong to only one whole
- Lifecycle dependency - parts are destroyed when whole is destroyed
Key Characteristics
Section titled “Key Characteristics”- Strong ownership - Container owns the parts
- Dependent lifecycle - Parts cannot exist without container
- Single ownership - Parts belong to only one container
- Filled diamond in UML diagrams
Basic Composition Example
Section titled “Basic Composition Example” 💡 Tip: Click dropdown to switch between languages
class Car: def __init__(self, brand: str, model: str): self.brand = brand self.model = model # Composition - Engine cannot exist without Car self.engine = Engine() # Created when Car is created self.wheels = [Wheel() for _ in range(4)] # Created with Car
def start(self): return self.engine.start()
class Engine: def __init__(self): self.running = False
def start(self): self.running = True return "Engine started"
class Wheel: def __init__(self): self.size = 16
# Engine and Wheels are created and destroyed with Carcar = Car("Toyota", "Camry")print(car.start()) # "Engine started"# When car is deleted, engine and wheels are also gonepublic class Car { private String brand; private String model; private Engine engine; // Composition - Engine cannot exist without Car private java.util.List<Wheel> wheels; // Composition - Wheels created with Car
public Car(String brand, String model) { this.brand = brand; this.model = model; // Created when Car is created this.engine = new Engine(); this.wheels = new java.util.ArrayList<>(); for (int i = 0; i < 4; i++) { wheels.add(new Wheel()); } }
public String start() { return engine.start(); }}
public class Engine { private boolean running;
public Engine() { this.running = false; }
public String start() { this.running = true; return "Engine started"; }}
public class Wheel { private int size;
public Wheel() { this.size = 16; }}
// Usagepublic class Main { public static void main(String[] args) { // Engine and Wheels are created and destroyed with Car Car car = new Car("Toyota", "Camry"); System.out.println(car.start()); // "Engine started" // When car is garbage collected, engine and wheels are also gone }}class Car { private brand: string; private model: string; private engine: Engine; // Composition - Engine cannot exist without Car private wheels: Wheel[]; // Composition - Wheels created with Car
constructor(brand: string, model: string) { this.brand = brand; this.model = model; // Created when Car is created this.engine = new Engine(); this.wheels = Array.from({ length: 4 }, () => new Wheel()); }
start(): string { return this.engine.start(); }}
class Engine { private running: boolean;
constructor() { this.running = false; }
start(): string { this.running = true; return "Engine started"; }}
class Wheel { private size: number;
constructor() { this.size = 16; }}
// Engine and Wheels are created and destroyed with Carconst car = new Car("Toyota", "Camry");console.log(car.start()); // "Engine started"// When car is garbage collected, engine and wheels are also gone#include <iostream>#include <string>#include <vector>
class Engine {private: bool running;
public: Engine() : running(false) {}
std::string start() { running = true; return "Engine started"; }};
class Wheel {private: int size;
public: Wheel() : size(16) {}};
class Car {private: std::string brand; std::string model; Engine engine; // Composition - Engine cannot exist without Car std::vector<Wheel> wheels; // Composition - Wheels created with Car
public: Car(const std::string& brand, const std::string& model) : brand(brand), model(model) { // Created when Car is created for (int i = 0; i < 4; i++) { wheels.push_back(Wheel()); } }
std::string start() { return engine.start(); }};
int main() { // Engine and Wheels are created and destroyed with Car Car car("Toyota", "Camry"); std::cout << car.start() << std::endl; // "Engine started" // When car goes out of scope, engine and wheels are also destroyed
return 0;}using System;using System.Collections.Generic;
public class Car{ private string brand; private string model; private Engine engine; // Composition - Engine cannot exist without Car private List<Wheel> wheels; // Composition - Wheels created with Car
public Car(string brand, string model) { this.brand = brand; this.model = model; // Created when Car is created this.engine = new Engine(); this.wheels = new List<Wheel>(); for (int i = 0; i < 4; i++) { wheels.Add(new Wheel()); } }
public string Start() { return engine.Start(); }}
public class Engine{ private bool running;
public Engine() { this.running = false; }
public string Start() { this.running = true; return "Engine started"; }}
public class Wheel{ private int size;
public Wheel() { this.size = 16; }}
class Program{ static void Main() { // Engine and Wheels are created and destroyed with Car Car car = new Car("Toyota", "Camry"); Console.WriteLine(car.Start()); // "Engine started" // When car is garbage collected, engine and wheels are also gone }}package main
import "fmt"
// Engine and wheels are created with Car and share its lifetime.
type Engine struct { running bool}
func (e *Engine) Start() string { e.running = true return "Engine started"}
type Wheel struct { size int}
func newWheel() *Wheel { return &Wheel{size: 16}}
type Car struct { brand, model string engine *Engine wheels []*Wheel}
func NewCar(brand, model string) *Car { wheels := make([]*Wheel, 4) for i := range wheels { wheels[i] = newWheel() } return &Car{ brand: brand, model: model, engine: &Engine{}, wheels: wheels, }}
func (c *Car) Start() string { return c.engine.Start()}
func main() { car := NewCar("Toyota", "Camry") fmt.Println(car.Start())}// Engine and wheels are owned by Car and share its lifetime.
struct Engine { running: bool,}
impl Engine { fn start(&mut self) -> &'static str { self.running = true; "Engine started" }}
struct Wheel { size: u32,}
struct Car { brand: String, model: String, engine: Engine, wheels: Vec<Wheel>,}
impl Car { fn new(brand: impl Into<String>, model: impl Into<String>) -> Self { Self { brand: brand.into(), model: model.into(), engine: Engine { running: false }, wheels: (0..4).map(|_| Wheel { size: 16 }).collect(), } }
fn start(&mut self) -> &'static str { self.engine.start() }}
fn main() { let mut car = Car::new("Toyota", "Camry"); println!("{}", car.start());} // car, engine, and wheels are dropped togetherVisual Representation
Section titled “Visual Representation”Real-World Example: Order System
Section titled “Real-World Example: Order System” 💡 Tip: Click dropdown to switch between languages
class Order: def __init__(self, order_id: str, customer_name: str): self.order_id = order_id self.customer_name = customer_name self.items = [] # Composition - OrderItems belong to Order self.shipping_address = Address("", "", "", "") # Composition
def add_item(self, product_name: str, quantity: int, price: float): item = OrderItem(product_name, quantity, price) # Created by Order self.items.append(item)
def set_shipping_address(self, street, city, state, zip_code): self.shipping_address = Address(street, city, state, zip_code)
def get_total(self): return sum(item.get_subtotal() for item in self.items)
class OrderItem: def __init__(self, product_name: str, quantity: int, price: float): self.product_name = product_name self.quantity = quantity self.price = price
def get_subtotal(self): return self.quantity * self.price
class Address: def __init__(self, street: str, city: str, state: str, zip_code: str): self.street = street self.city = city self.state = state self.zip_code = zip_code
# OrderItems and Address are created with Orderorder = Order("ORD-001", "Alice")order.add_item("Laptop", 1, 999.99)order.add_item("Mouse", 2, 29.99)order.set_shipping_address("123 Main St", "SF", "CA", "94102")
print(f"Order total: ${order.get_total():.2f}") # $1059.97# When order is deleted, items and address are also gonepublic class Order { private String orderId; private String customerName; private java.util.List<OrderItem> items; // Composition - OrderItems belong to Order private Address shippingAddress; // Composition
public Order(String orderId, String customerName) { this.orderId = orderId; this.customerName = customerName; this.items = new java.util.ArrayList<>(); this.shippingAddress = new Address("", "", "", ""); }
public void addItem(String productName, int quantity, double price) { OrderItem item = new OrderItem(productName, quantity, price); // Created by Order items.add(item); }
public void setShippingAddress(String street, String city, String state, String zipCode) { this.shippingAddress = new Address(street, city, state, zipCode); }
public double getTotal() { return items.stream() .mapToDouble(OrderItem::getSubtotal) .sum(); }}
public class OrderItem { private String productName; private int quantity; private double price;
public OrderItem(String productName, int quantity, double price) { this.productName = productName; this.quantity = quantity; this.price = price; }
public double getSubtotal() { return quantity * price; }}
public class Address { private String street; private String city; private String state; private String zipCode;
public Address(String street, String city, String state, String zipCode) { this.street = street; this.city = city; this.state = state; this.zipCode = zipCode; }}
// Usagepublic class Main { public static void main(String[] args) { // OrderItems and Address are created with Order Order order = new Order("ORD-001", "Alice"); order.addItem("Laptop", 1, 999.99); order.addItem("Mouse", 2, 29.99); order.setShippingAddress("123 Main St", "SF", "CA", "94102");
System.out.printf("Order total: $%.2f%n", order.getTotal()); // $1059.97 // When order is garbage collected, items and address are also gone }}class Order { private orderId: string; private customerName: string; private items: OrderItem[]; // Composition - OrderItems belong to Order private shippingAddress: Address; // Composition
constructor(orderId: string, customerName: string) { this.orderId = orderId; this.customerName = customerName; this.items = []; this.shippingAddress = new Address("", "", "", ""); }
addItem(productName: string, quantity: number, price: number): void { const item = new OrderItem(productName, quantity, price); // Created by Order this.items.push(item); }
setShippingAddress(street: string, city: string, state: string, zipCode: string): void { this.shippingAddress = new Address(street, city, state, zipCode); }
getTotal(): number { return this.items.reduce((sum, item) => sum + item.getSubtotal(), 0); }}
class OrderItem { constructor( private productName: string, private quantity: number, private price: number ) {}
getSubtotal(): number { return this.quantity * this.price; }}
class Address { constructor( private street: string, private city: string, private state: string, private zipCode: string ) {}}
// OrderItems and Address are created with Orderconst order = new Order("ORD-001", "Alice");order.addItem("Laptop", 1, 999.99);order.addItem("Mouse", 2, 29.99);order.setShippingAddress("123 Main St", "SF", "CA", "94102");
console.log(`Order total: $${order.getTotal().toFixed(2)}`); // $1059.97// When order is garbage collected, items and address are also gone#include <iostream>#include <string>#include <vector>#include <iomanip>
class OrderItem {private: std::string productName; int quantity; double price;
public: OrderItem(const std::string& productName, int quantity, double price) : productName(productName), quantity(quantity), price(price) {}
double getSubtotal() const { return quantity * price; }};
class Address {private: std::string street; std::string city; std::string state; std::string zipCode;
public: Address(const std::string& street, const std::string& city, const std::string& state, const std::string& zipCode) : street(street), city(city), state(state), zipCode(zipCode) {}};
class Order {private: std::string orderId; std::string customerName; std::vector<OrderItem> items; // Composition - OrderItems belong to Order Address shippingAddress; // Composition
public: Order(const std::string& orderId, const std::string& customerName) : orderId(orderId), customerName(customerName), shippingAddress("", "", "", "") {}
void addItem(const std::string& productName, int quantity, double price) { items.emplace_back(productName, quantity, price); // Created by Order }
void setShippingAddress(const std::string& street, const std::string& city, const std::string& state, const std::string& zipCode) { shippingAddress = Address(street, city, state, zipCode); }
double getTotal() const { double total = 0.0; for (const auto& item : items) { total += item.getSubtotal(); } return total; }};
int main() { // OrderItems and Address are created with Order Order order("ORD-001", "Alice"); order.addItem("Laptop", 1, 999.99); order.addItem("Mouse", 2, 29.99); order.setShippingAddress("123 Main St", "SF", "CA", "94102");
std::cout << std::fixed << std::setprecision(2); std::cout << "Order total: $" << order.getTotal() << std::endl; // $1059.97 // When order goes out of scope, items and address are also destroyed
return 0;}using System;using System.Collections.Generic;using System.Linq;
public class Order{ private string orderId; private string customerName; private List<OrderItem> items; // Composition - OrderItems belong to Order private Address shippingAddress; // Composition
public Order(string orderId, string customerName) { this.orderId = orderId; this.customerName = customerName; this.items = new List<OrderItem>(); this.shippingAddress = new Address("", "", "", ""); }
public void AddItem(string productName, int quantity, double price) { OrderItem item = new OrderItem(productName, quantity, price); // Created by Order items.Add(item); }
public void SetShippingAddress(string street, string city, string state, string zipCode) { this.shippingAddress = new Address(street, city, state, zipCode); }
public double GetTotal() { return items.Sum(item => item.GetSubtotal()); }}
public class OrderItem{ private string productName; private int quantity; private double price;
public OrderItem(string productName, int quantity, double price) { this.productName = productName; this.quantity = quantity; this.price = price; }
public double GetSubtotal() { return quantity * price; }}
public class Address{ private string street; private string city; private string state; private string zipCode;
public Address(string street, string city, string state, string zipCode) { this.street = street; this.city = city; this.state = state; this.zipCode = zipCode; }}
class Program{ static void Main() { // OrderItems and Address are created with Order Order order = new Order("ORD-001", "Alice"); order.AddItem("Laptop", 1, 999.99); order.AddItem("Mouse", 2, 29.99); order.SetShippingAddress("123 Main St", "SF", "CA", "94102");
Console.WriteLine($"Order total: ${order.GetTotal():F2}"); // $1059.97 // When order is garbage collected, items and address are also gone }}package main
import ( "fmt")
type OrderItem struct { productName string quantity int price float64}
func NewOrderItem(productName string, quantity int, price float64) *OrderItem { return &OrderItem{productName: productName, quantity: quantity, price: price}}
func (oi *OrderItem) Subtotal() float64 { return float64(oi.quantity) * oi.price}
type Address struct { street, city, state, zipCode string}
type Order struct { orderID string customerName string items []*OrderItem shippingAddress Address}
func NewOrder(orderID, customerName string) *Order { return &Order{ orderID: orderID, customerName: customerName, items: []*OrderItem{}, shippingAddress: Address{}, }}
func (o *Order) AddItem(productName string, quantity int, price float64) { o.items = append(o.items, NewOrderItem(productName, quantity, price))}
func (o *Order) SetShippingAddress(street, city, state, zipCode string) { o.shippingAddress = Address{street: street, city: city, state: state, zipCode: zipCode}}
func (o *Order) Total() float64 { var sum float64 for _, it := range o.items { sum += it.Subtotal() } return sum}
func main() { order := NewOrder("ORD-001", "Alice") order.AddItem("Laptop", 1, 999.99) order.AddItem("Mouse", 2, 29.99) order.SetShippingAddress("123 Main St", "SF", "CA", "94102") fmt.Printf("Order total: $%.2f\n", order.Total())}struct OrderItem { product_name: String, quantity: u32, price: f64,}
impl OrderItem { fn new(product_name: impl Into<String>, quantity: u32, price: f64) -> Self { Self { product_name: product_name.into(), quantity, price, } }
fn subtotal(&self) -> f64 { self.quantity as f64 * self.price }}
#[derive(Default)]struct Address { street: String, city: String, state: String, zip_code: String,}
struct Order { order_id: String, customer_name: String, items: Vec<OrderItem>, // Composition - OrderItems belong to Order shipping_address: Address, // Composition}
impl Order { fn new(order_id: impl Into<String>, customer_name: impl Into<String>) -> Self { Self { order_id: order_id.into(), customer_name: customer_name.into(), items: Vec::new(), shipping_address: Address::default(), } }
fn add_item(&mut self, product_name: impl Into<String>, quantity: u32, price: f64) { self.items.push(OrderItem::new(product_name, quantity, price)); }
fn set_shipping_address( &mut self, street: impl Into<String>, city: impl Into<String>, state: impl Into<String>, zip_code: impl Into<String>, ) { self.shipping_address = Address { street: street.into(), city: city.into(), state: state.into(), zip_code: zip_code.into(), }; }
fn total(&self) -> f64 { self.items.iter().map(OrderItem::subtotal).sum() }}
fn main() { let mut order = Order::new("ORD-001", "Alice"); order.add_item("Laptop", 1, 999.99); order.add_item("Mouse", 2, 29.99); order.set_shipping_address("123 Main St", "SF", "CA", "94102");
println!("Order total: ${:.2}", order.total());} // order owns and drops its items/addressComposition vs Aggregation
Section titled “Composition vs Aggregation”| Feature | Composition | Aggregation |
|---|---|---|
| Ownership | Strong | Weak |
| Lifecycle | Dependent | Independent |
| Multi-ownership | No | Yes |
| UML Symbol | Filled diamond | Hollow diamond |
| Example | Car → Engine | University → Students |
Key Takeaways
Section titled “Key Takeaways”When to Use Composition
Section titled “When to Use Composition”Use Composition when:
- Parts cannot exist without the whole
- Parts belong to only one whole
- Lifecycle is tied together
- Relationship is essential and permanent
- Parts are created by the whole
Examples:
- Car → Engine (engine doesn’t exist without car)
- Order → OrderItems (items don’t exist without order)
- House → Room (rooms don’t exist without house)
- Document → Paragraph (paragraphs don’t exist without document)
- Computer → CPU (CPU doesn’t exist without computer)