Aggregation
Weak ownership - parts can exist without the whole.
Aggregation is a “has-a” relationship where one class contains another, but the contained class can exist independently. It represents a weak ownership relationship.
What is Aggregation?
Section titled “What is Aggregation?”Aggregation represents:
- “Has-a” relationship (weak ownership)
- Parts can exist independently of the whole
- Parts can belong to multiple wholes
- Lifecycle independence - parts aren’t destroyed when whole is destroyed
Key Characteristics
Section titled “Key Characteristics”- Weak ownership - Container doesn’t own the parts
- Independent lifecycle - Parts can exist without container
- Multi-ownership - Parts can belong to multiple containers
- Hollow diamond in UML diagrams
Basic Aggregation Example
Section titled “Basic Aggregation Example” 💡 Tip: Click dropdown to switch between languages
class University: def __init__(self, name: str): self.name = name self.students = [] # Aggregation - students can exist without university
def add_student(self, student): self.students.append(student)
class Student: def __init__(self, name: str, student_id: str): self.name = name self.student_id = student_id # Student can exist without being in a university
# Students can exist independentlystudent1 = Student("Alice", "S001")student2 = Student("Bob", "S002")
university = University("MIT")university.add_student(student1)university.add_student(student2)
# Students still exist even if university is deleteddel university# student1 and student2 still existpublic class University { private String name; private java.util.List<Student> students; // Aggregation - students can exist without university
public University(String name) { this.name = name; this.students = new java.util.ArrayList<>(); }
public void addStudent(Student student) { students.add(student); }}
public class Student { private String name; private String studentId;
// Student can exist without being in a university public Student(String name, String studentId) { this.name = name; this.studentId = studentId; }}
// Usagepublic class Main { public static void main(String[] args) { // Students can exist independently Student student1 = new Student("Alice", "S001"); Student student2 = new Student("Bob", "S002");
University university = new University("MIT"); university.addStudent(student1); university.addStudent(student2);
// Students still exist even if university reference is null university = null; // student1 and student2 still exist }}class University { private name: string; private students: Student[]; // Aggregation - students can exist without university
constructor(name: string) { this.name = name; this.students = []; }
addStudent(student: Student): void { this.students.push(student); }}
class Student { private name: string; private studentId: string;
// Student can exist without being in a university constructor(name: string, studentId: string) { this.name = name; this.studentId = studentId; }}
// Students can exist independentlyconst student1 = new Student("Alice", "S001");const student2 = new Student("Bob", "S002");
let university: University | null = new University("MIT");university.addStudent(student1);university.addStudent(student2);
// Students still exist even if university is set to nulluniversity = null;// student1 and student2 still exist#include <iostream>#include <string>#include <vector>
class Student {private: std::string name; std::string studentId;
public: // Student can exist without being in a university Student(const std::string& name, const std::string& studentId) : name(name), studentId(studentId) {}
std::string getName() const { return name; }};
class University {private: std::string name; std::vector<Student*> students; // Aggregation - students can exist without university
public: University(const std::string& name) : name(name) {}
void addStudent(Student* student) { students.push_back(student); }};
int main() { // Students can exist independently Student student1("Alice", "S001"); Student student2("Bob", "S002");
{ University university("MIT"); university.addStudent(&student1); university.addStudent(&student2);
// University goes out of scope and is destroyed } // student1 and student2 still exist
std::cout << "Students still exist: " << student1.getName() << std::endl;
return 0;}using System;using System.Collections.Generic;
public class University{ private string name; private List<Student> students; // Aggregation - students can exist without university
public University(string name) { this.name = name; this.students = new List<Student>(); }
public void AddStudent(Student student) { students.Add(student); }}
public class Student{ private string name; private string studentId;
// Student can exist without being in a university public Student(string name, string studentId) { this.name = name; this.studentId = studentId; }}
class Program{ static void Main() { // Students can exist independently Student student1 = new Student("Alice", "S001"); Student student2 = new Student("Bob", "S002");
University university = new University("MIT"); university.AddStudent(student1); university.AddStudent(student2);
// Students still exist even if university reference is null university = null; // student1 and student2 still exist }}package main
// University aggregates Students — students exist without the university.
type University struct { name string students []*Student}
func NewUniversity(name string) *University { return &University{name: name}}
func (u *University) AddStudent(student *Student) { u.students = append(u.students, student)}
type Student struct { name string studentID string}
func NewStudent(name, studentID string) *Student { return &Student{name: name, studentID: studentID}}
func main() { student1 := NewStudent("Alice", "S001") student2 := NewStudent("Bob", "S002")
university := NewUniversity("MIT") university.AddStudent(student1) university.AddStudent(student2)
_ = university // drop reference — students remain valid independently}// University aggregates Students — students exist without the university.
struct Student { name: String, student_id: String,}
impl Student { fn new(name: impl Into<String>, student_id: impl Into<String>) -> Self { Self { name: name.into(), student_id: student_id.into(), } }}
struct University<'a> { name: String, students: Vec<&'a Student>,}
impl<'a> University<'a> { fn new(name: impl Into<String>) -> Self { Self { name: name.into(), students: Vec::new(), } }
fn add_student(&mut self, student: &'a Student) { self.students.push(student); }}
fn main() { let student1 = Student::new("Alice", "S001"); let student2 = Student::new("Bob", "S002");
{ let mut university = University::new("MIT"); university.add_student(&student1); university.add_student(&student2); } // university is dropped here
// student1 and student2 still exist independently println!("Students still exist: {}, {}", student1.name, student2.name);}Visual Representation
Section titled “Visual Representation”Real-World Example: Shopping Cart
Section titled “Real-World Example: Shopping Cart” 💡 Tip: Click dropdown to switch between languages
class Product: def __init__(self, sku: str, name: str, price: float): self.sku = sku self.name = name self.price = price
class ShoppingCart: def __init__(self, customer_name: str): self.customer_name = customer_name self.items = [] # Aggregation - products exist independently
def add_item(self, product, quantity: int = 1): self.items.append({"product": product, "quantity": quantity})
def get_total(self): return sum(item["product"].price * item["quantity"] for item in self.items)
# Products exist independentlylaptop = Product("LAP-001", "Laptop", 999.99)mouse = Product("MOU-001", "Mouse", 29.99)
# Multiple carts can reference same productscart1 = ShoppingCart("Alice")cart2 = ShoppingCart("Bob")
cart1.add_item(laptop, 1)cart1.add_item(mouse, 2)
cart2.add_item(laptop, 1) # Same product in different cart
print(f"Cart 1 total: ${cart1.get_total():.2f}") # $1059.97print(f"Cart 2 total: ${cart2.get_total():.2f}") # $999.99public class Product { private String sku; private String name; private double price;
public Product(String sku, String name, double price) { this.sku = sku; this.name = name; this.price = price; }
public double getPrice() { return price; }}
public class CartItem { private Product product; private int quantity;
public CartItem(Product product, int quantity) { this.product = product; this.quantity = quantity; }
public double getSubtotal() { return product.getPrice() * quantity; }}
public class ShoppingCart { private String customerName; private java.util.List<CartItem> items; // Aggregation - products exist independently
public ShoppingCart(String customerName) { this.customerName = customerName; this.items = new java.util.ArrayList<>(); }
public void addItem(Product product, int quantity) { items.add(new CartItem(product, quantity)); }
public double getTotal() { return items.stream() .mapToDouble(CartItem::getSubtotal) .sum(); }}
// Usagepublic class Main { public static void main(String[] args) { // Products exist independently Product laptop = new Product("LAP-001", "Laptop", 999.99); Product mouse = new Product("MOU-001", "Mouse", 29.99);
// Multiple carts can reference same products ShoppingCart cart1 = new ShoppingCart("Alice"); ShoppingCart cart2 = new ShoppingCart("Bob");
cart1.addItem(laptop, 1); cart1.addItem(mouse, 2);
cart2.addItem(laptop, 1); // Same product in different cart
System.out.printf("Cart 1 total: $%.2f%n", cart1.getTotal()); // $1059.97 System.out.printf("Cart 2 total: $%.2f%n", cart2.getTotal()); // $999.99 }}class Product { constructor( public sku: string, public name: string, public price: number ) {}}
interface CartItem { product: Product; quantity: number;}
class ShoppingCart { private customerName: string; private items: CartItem[]; // Aggregation - products exist independently
constructor(customerName: string) { this.customerName = customerName; this.items = []; }
addItem(product: Product, quantity: number = 1): void { this.items.push({ product, quantity }); }
getTotal(): number { return this.items.reduce((sum, item) => sum + item.product.price * item.quantity, 0 ); }}
// Products exist independentlyconst laptop = new Product("LAP-001", "Laptop", 999.99);const mouse = new Product("MOU-001", "Mouse", 29.99);
// Multiple carts can reference same productsconst cart1 = new ShoppingCart("Alice");const cart2 = new ShoppingCart("Bob");
cart1.addItem(laptop, 1);cart1.addItem(mouse, 2);
cart2.addItem(laptop, 1); // Same product in different cart
console.log(`Cart 1 total: $${cart1.getTotal().toFixed(2)}`); // $1059.97console.log(`Cart 2 total: $${cart2.getTotal().toFixed(2)}`); // $999.99#include <iostream>#include <string>#include <vector>#include <iomanip>
class Product {private: std::string sku; std::string name; double price;
public: Product(const std::string& sku, const std::string& name, double price) : sku(sku), name(name), price(price) {}
double getPrice() const { return price; }};
struct CartItem { Product* product; int quantity;
CartItem(Product* product, int quantity) : product(product), quantity(quantity) {}
double getSubtotal() const { return product->getPrice() * quantity; }};
class ShoppingCart {private: std::string customerName; std::vector<CartItem> items; // Aggregation - products exist independently
public: ShoppingCart(const std::string& customerName) : customerName(customerName) {}
void addItem(Product* product, int quantity) { items.push_back(CartItem(product, quantity)); }
double getTotal() const { double total = 0.0; for (const auto& item : items) { total += item.getSubtotal(); } return total; }};
int main() { // Products exist independently Product laptop("LAP-001", "Laptop", 999.99); Product mouse("MOU-001", "Mouse", 29.99);
// Multiple carts can reference same products ShoppingCart cart1("Alice"); ShoppingCart cart2("Bob");
cart1.addItem(&laptop, 1); cart1.addItem(&mouse, 2);
cart2.addItem(&laptop, 1); // Same product in different cart
std::cout << std::fixed << std::setprecision(2); std::cout << "Cart 1 total: $" << cart1.getTotal() << std::endl; // $1059.97 std::cout << "Cart 2 total: $" << cart2.getTotal() << std::endl; // $999.99
return 0;}using System;using System.Collections.Generic;using System.Linq;
public class Product{ public string Sku { get; } public string Name { get; } public double Price { get; }
public Product(string sku, string name, double price) { Sku = sku; Name = name; Price = price; }}
public class CartItem{ public Product Product { get; } public int Quantity { get; }
public CartItem(Product product, int quantity) { Product = product; Quantity = quantity; }
public double GetSubtotal() { return Product.Price * Quantity; }}
public class ShoppingCart{ private string customerName; private List<CartItem> items; // Aggregation - products exist independently
public ShoppingCart(string customerName) { this.customerName = customerName; this.items = new List<CartItem>(); }
public void AddItem(Product product, int quantity) { items.Add(new CartItem(product, quantity)); }
public double GetTotal() { return items.Sum(item => item.GetSubtotal()); }}
class Program{ static void Main() { // Products exist independently Product laptop = new Product("LAP-001", "Laptop", 999.99); Product mouse = new Product("MOU-001", "Mouse", 29.99);
// Multiple carts can reference same products ShoppingCart cart1 = new ShoppingCart("Alice"); ShoppingCart cart2 = new ShoppingCart("Bob");
cart1.AddItem(laptop, 1); cart1.AddItem(mouse, 2);
cart2.AddItem(laptop, 1); // Same product in different cart
Console.WriteLine($"Cart 1 total: ${cart1.GetTotal():F2}"); // $1059.97 Console.WriteLine($"Cart 2 total: ${cart2.GetTotal():F2}"); // $999.99 }}package main
import "fmt"
type Product struct { SKU string Name string Price float64}
func NewProduct(sku, name string, price float64) *Product { return &Product{SKU: sku, Name: name, Price: price}}
type CartItem struct { Product *Product Quantity int}
func (ci *CartItem) Subtotal() float64 { return ci.Product.Price * float64(ci.Quantity)}
type ShoppingCart struct { customerName string items []*CartItem}
func NewShoppingCart(customerName string) *ShoppingCart { return &ShoppingCart{customerName: customerName}}
func (c *ShoppingCart) AddItem(product *Product, quantity int) { c.items = append(c.items, &CartItem{Product: product, Quantity: quantity})}
func (c *ShoppingCart) Total() float64 { var sum float64 for _, it := range c.items { sum += it.Subtotal() } return sum}
func main() { laptop := NewProduct("LAP-001", "Laptop", 999.99) mouse := NewProduct("MOU-001", "Mouse", 29.99)
cart1 := NewShoppingCart("Alice") cart2 := NewShoppingCart("Bob")
cart1.AddItem(laptop, 1) cart1.AddItem(mouse, 2)
cart2.AddItem(laptop, 1)
fmt.Printf("Cart 1 total: $%.2f\n", cart1.Total()) fmt.Printf("Cart 2 total: $%.2f\n", cart2.Total())}struct Product { sku: String, name: String, price: f64,}
impl Product { fn new(sku: impl Into<String>, name: impl Into<String>, price: f64) -> Self { Self { sku: sku.into(), name: name.into(), price, } }}
struct CartItem<'a> { product: &'a Product, quantity: u32,}
impl<'a> CartItem<'a> { fn subtotal(&self) -> f64 { self.product.price * self.quantity as f64 }}
struct ShoppingCart<'a> { customer_name: String, items: Vec<CartItem<'a>>, // Aggregation - products exist independently}
impl<'a> ShoppingCart<'a> { fn new(customer_name: impl Into<String>) -> Self { Self { customer_name: customer_name.into(), items: Vec::new(), } }
fn add_item(&mut self, product: &'a Product, quantity: u32) { self.items.push(CartItem { product, quantity }); }
fn total(&self) -> f64 { self.items.iter().map(CartItem::subtotal).sum() }}
fn main() { let laptop = Product::new("LAP-001", "Laptop", 999.99); let mouse = Product::new("MOU-001", "Mouse", 29.99);
let mut cart1 = ShoppingCart::new("Alice"); let mut cart2 = ShoppingCart::new("Bob");
cart1.add_item(&laptop, 1); cart1.add_item(&mouse, 2); cart2.add_item(&laptop, 1); // Same product in different cart
println!("Cart 1 total: ${:.2}", cart1.total()); println!("Cart 2 total: ${:.2}", cart2.total());}Aggregation vs Composition
Section titled “Aggregation vs Composition”| Feature | Aggregation | Composition |
|---|---|---|
| Ownership | Weak | Strong |
| Lifecycle | Independent | Dependent |
| Multi-ownership | Yes | No |
| UML Symbol | Hollow diamond | Filled diamond |
| Example | University → Students | Car → Engine |
Key Takeaways
Section titled “Key Takeaways”When to Use Aggregation
Section titled “When to Use Aggregation”Use Aggregation when:
- Parts can exist without the whole
- Parts can belong to multiple wholes
- You need flexibility in relationships
- Parts have independent lifecycle
- Relationship is “part-of” but not essential
Examples:
- University → Students (students can transfer)
- Shopping Cart → Products (products exist independently)
- Team → Players (players can change teams)
- Library → Books (books can be removed)
- Department → Employees (employees can move departments)