Association และ Aggregation เป็นความสัมพันธ์ระหว่างคลาส (Class Relationships) ใน Object-Oriented Design ทั้งคู่เป็นรูปแบบของ "Has-a" relationship แตกต่างกันที่ "ความแข็งแกร่ง" และ "Lifecycle"
Association (การเชื่อมโยง)
ความสัมพันธ์ "Has-a" แบบทั่วไป - คลาสหนึ่งใช้/รู้จักอีกคลาสหนึ่ง
- Object สามารถมีอยู่ได้แยกกัน (Independent lifecycle)
- ไม่มี ownership - ไม่มีฝ่ายใดเป็นเจ้าของอีกฝ่าย
- Multiplicity: 1-1, 1-*, *-* ได้ทั้งคู่
- UML: เส้นทึบ (solid line) ระหว่างคลาส
// Association: Employee "has a" Address
// Address สามารถมีอยู่ได้โดยไม่ต้องมี Employee
// Employee ถูกลบ Address ยังคงอยู่ได้
class Employee {
private String name;
private Address communicationAddress; // Reference to Address
public Employee(String name, Address address) {
this.name = name;
this.communicationAddress = address;
}
}
class Address {
private String street;
private String city;
private String zipCode;
public Address(String street, String city, String zipCode) {
this.street = street;
this.city = city;
this.zipCode = zipCode;
}
}
// Usage
Address addr = new Address("123 Main St", "Bangkok", "10110");
Employee emp = new Employee("John", addr);
// addr ยังใช้งานได้ต่อ แม้ emp จะถูก garbage collected
Aggregation (การรวม/การประกอบ - "Has-a" แบบมี Owner)
ความสัมพันธ์ "Has-a" แบบ Whole-Part โดย "Whole" (Container) เป็นเจ้าของ "Part" (Contained)
- Weak ownership: Whole จัดการ lifecycle ของ Parts แต่ Parts สามารถมีอยู่ได้แยกต่างหาก
- Part อาจถูก share ระหว่างหลาย Wholes ได้
- Multiplicity: Whole 1 : Part * (มักเป็น Collection)
- UML: เส้นทึบ + Empty Diamond (◇) ที่ฝั่ง Whole
// Aggregation: Department "has a" collection of Employees
// Department จัดการ employees แต่ Employee อาจย้าย department ได้
// หาก Department ถูกลบ Employee ยังคงอยู่ได้ (อาจย้ายไป dept อื่น)
class Department {
private String name;
private List<Employee> employees = new ArrayList<>(); // Collection of parts
public Department(String name) {
this.name = name;
}
public void addEmployee(Employee emp) {
employees.add(emp);
emp.setDepartment(this); // Bidirectional link (optional)
}
public void removeEmployee(Employee emp) {
employees.remove(emp);
emp.setDepartment(null);
}
}
class Employee {
private String name;
private Department department; // Back reference (optional)
public Employee(String name) { this.name = name; }
public void setDepartment(Department dept) { this.department = dept; }
public Department getDepartment() { return department; }
}
// Usage
Employee e1 = new Employee("Alice");
Employee e2 = new Employee("Bob");
Department dept = new Department("Engineering");
dept.addEmployee(e1);
dept.addEmployee(e2);
// e1, e2 ยังมีชีวิตอยู่ แม้ dept จะถูกลบ (ถ้ามี reference อื่น)
Composition (เพิ่มเติม - "Strong Aggregation")
รูปแบบสุดสุดของ Aggregation ที่ Part ไม่สามารถมีอยู่ได้โดยไม่มี Whole
- Strong ownership: Whole สร้างและทำลาย Parts
- Part ไม่สามารถ share ได้
- UML: เส้นทึบ + Filled Diamond (◆) ที่ฝั่ง Whole
// Composition: House "has a" Rooms
// Room ไม่มีอยู่ได้โดยไม่มี House
class House {
private List<Room> rooms = new ArrayList<>();
public House() {
// House สร้าง Rooms เอง
rooms.add(new Room("Bedroom", 20));
rooms.add(new Room("Kitchen", 15));
}
// ไม่มี setter สำหรับ rooms - ไม่ให้เพิ่ม/ล้างจากนอก
}
class Room {
private String name;
private double area;
// Package-private constructor - สร้างได้แค่ใน House
Room(String name, double area) {
this.name = name;
this.area = area;
}
}
ตารางเปรียบเทียบ Association vs Aggregation vs Composition
| คุณสมบัติ | Association | Aggregation | Composition |
|---|---|---|---|
| Relationship | Uses-a / Knows-a | Has-a (Weak ownership) | Has-a (Strong ownership) |
| Lifecycle Dependency | Independent | Part อาจอยู่ได้แยกจาก Whole | Part ต้องอยู่กับ Whole เท่านั้น |
| Ownership | ไม่มี | Shared/Weak | Exclusive/Strong |
| Part สามารถ Share ได้? | ได้ (N-Ary association) | ได้ | ไม่ได้ |
| Whole สร้าง Part? | ไม่จำเป็น | อาจสร้างหรือรับจากนอก | ต้องสร้างเอง |
| Whole ทำลาย Part? | ไม่ | ไม่ (Part อาจย้ายไป Whole อื่น) | ใช่ (เมื่อ Whole ตาย Part ตาย) |
| UML Notation | ──────── | ◇──────── | ◆──────── |
| Java Implementation | Reference field | Collection field (List/Set) | Collection field + private constructor + no setters |
| Example | Employee ↔ Address | Department ↔ Employees | House ↔ Rooms, Order ↔ OrderItems |
Java Implementation Patterns
Association (Bidirectional)
class Student {
private String name;
private Course course; // Single reference
public void enroll(Course c) {
this.course = c;
c.addStudent(this); // Maintain bidirectional consistency
}
}
class Course {
private List<Student> students = new ArrayList<>();
public void addStudent(Student s) { students.add(s); }
}
Aggregation (Department-Employee)
class Department {
private List<Employee> employees = new ArrayList<>();
public void addEmployee(Employee e) {
if (!employees.contains(e)) {
employees.add(e);
e.setDepartment(this);
}
}
}
Composition (Order-OrderItem)
class Order {
private List<OrderItem> items = new ArrayList<>();
public void addItem(Product product, int qty) {
// Order สร้าง OrderItem เอง - ไม่อนุญาตสร้างจากนอก
items.add(new OrderItem(this, product, qty));
}
// ไม่มี setItems(), ไม่มี public OrderItem constructor
}
class OrderItem {
private final Order order; // final - set ครั้งเดียวตอนสร้าง
private final Product product;
private int quantity;
// Package-private - ใช้ได้แค่ใน package เดียวกับ Order
OrderItem(Order order, Product product, int quantity) {
this.order = order;
this.product = product;
this.quantity = quantity;
}
}
Best Practices
- ใช้ Composition over Inheritance (GoF) - Composition ให้ flexibility มากกว่า
- เลือก Association เมื่อ objects มีชีวิตอยู่แยกกันได้จริง (เช่น Customer - Address)
- เลือก Aggregation เมื่อเป็น Whole-Part แต่ Part reusable (เช่น Library - Books)
- เลือก Composition เมื่อ Part ไม่มีความหมายโดยลำพัง (เช่น Order - OrderItem, House - Room)
- ใน Java: ใช้
finalfields, private constructors, defensive copying เพื่อ enforce ownership