반응형
객체 지향 설계를 할 때 클래스 간의 관계를 명확하게 표현하는 것은 매우 중요합니다. UML(Unified Modeling Language)은 이러한 관계들을 체계적으로 표현할 수 있는 표준 표기법을 제공합니다. 오늘은 자주 혼동되는 UML 관계 표기법들을 정확히 알아보겠습니다.
🔍 흔한 오해: 상속은 연관 관계가 아닙니다!
많은 개발자들이 Java의 상속 관계를 UML의 **연관 관계(Association)**로 표현하곤 합니다. 하지만 이는 잘못된 접근입니다. 상속은 특별한 관계이므로 별도의 표기법을 사용해야 합니다.
// 이것은 상속 관계입니다
class Animal {
void eat() { ... }
}
class Dog extends Animal {
void bark() { ... }
}
위 코드의 Dog extends Animal은 연관 관계가 아닌 상속 관계입니다!
1. 일반화 관계 (Generalization) - 상속
📋 정의
UML에서 상속 관계는 **일반화(Generalization)**라고 부르며, "is-a" 관계를 나타냅니다.
🎨 표기법
- 실선 화살표: 자식 클래스에서 부모 클래스를 가리키는 화살표
- 화살표 머리: 속이 빈 삼각형 사용
- 방향: 화살표는 부모 클래스 쪽을 가리킴
Animal
△
| (속이 빈 삼각형 화살표)
|
Dog
💻 Java 코드 예시
// 일반화 관계의 전형적인 예
abstract class Vehicle {
protected String brand;
protected int speed;
public abstract void start();
public void stop() {
System.out.println("정지합니다");
}
}
class Car extends Vehicle {
private int doors;
@Override
public void start() {
System.out.println("시동을 켭니다");
}
public void honk() {
System.out.println("경적을 울립니다");
}
}
class Motorcycle extends Vehicle {
@Override
public void start() {
System.out.println("킥으로 시동을 켭니다");
}
}
의미: "Car는 Vehicle이다", "Motorcycle은 Vehicle이다"
2. 연관 관계 (Association) - 일반적인 참조
📋 정의
한 클래스가 다른 클래스를 참조하는 가장 기본적인 관계입니다.
🎨 표기법
- 실선: 단순한 실선으로 연결
- 화살표: 방향성이 있는 경우 일반 화살표 사용
- 양방향: 화살표 없이 실선만 사용
Student ————————————> Course
(실선 화살표)
Student ———————————— Teacher
(양방향 실선)
💻 Java 코드 예시
class Student {
private String name;
private List<Course> courses; // Student가 Course를 참조
public void enrollCourse(Course course) { // 학생은 강의를 신청한다.
courses.add(course);
}
}
class Course {
private String courseName;
private Teacher teacher; // Course가 Teacher를 참조 (Teacher 객체를 필드로 사용)
}
class Teacher {
private String name;
private List<Course> teachingCourses; // 양방향 참조
}
3. 집합 관계 (Aggregation) - 약한 포함
📋 정의
약한 포함 관계로, 전체 객체가 사라져도 부분 객체는 독립적으로 존재할 수 있습니다.
🎨 표기법
- 속이 빈 다이아몬드: 전체 객체 쪽에 위치
- 실선: 다이아몬드에서 부분 객체로 연결
Department ◇————————————> Student
(속이 빈 다이아몬드)
💻 Java 코드 예시
class Department {
private String name;
private List<Student> students; // 학생들을 포함
public void addStudent(Student student) {
students.add(student);
}
public void removeDepartment() {
// 학과가 없어져도 학생들은 여전히 존재할 수 있음
students.clear(); // 참조만 제거 : 학생 객체가 없어지는 것은 아님!
}
}
class Student {
private String name;
private String studentId;
// 학생은 학과와 독립적으로 존재 가능
public void transferToOtherDepartment() {
// 다른 학과로 이전 가능
}
}
class University {
public static void main(String[] args) {
Department cs = new Department("컴퓨터공학과");
Student john = new Student("John", "20230001");
cs.addStudent(john);
cs.removeDepartment(); // 학과 제거
// john 객체는 여전히 존재하고 다른 학과에 배정 가능
Department ee = new Department("전자공학과");
ee.addStudent(john);
}
}
🔍 특징
- 생명주기 독립성: 부분 객체의 생명주기가 전체 객체와 독립적
- 재사용 가능: 부분 객체를 다른 전체 객체와 연결 가능
- "has-a" 관계: "학과는 학생들을 가지고 있다"
4. 합성 관계 (Composition) - 강한 포함
📋 정의
강한 포함 관계로, 전체 객체가 사라지면 부분 객체도 함께 사라집니다.
🎨 표기법
- 속이 채워진 검은 다이아몬드: 전체 객체 쪽에 위치
- 실선: 다이아몬드에서 부분 객체로 연결
Car ◆————————————> Engine
(속이 채워진 다이아몬드)
💻 Java 코드 예시
class Car {
private String model;
private Engine engine; // 엔진을 포함 (강한 포함)
private List<Wheel> wheels; // 바퀴들을 포함
public Car(String model) {
this.model = model;
// Car 생성 시 Engine도 함께 생성 (강한 결합)
this.engine = new Engine("V6", 300);
this.wheels = new ArrayList<>();
for (int i = 0; i < 4; i++) {
wheels.add(new Wheel(17)); // 바퀴도 Car와 함께 생성
}
}
public void start() {
engine.start();
}
// Car가 소멸되면 Engine과 Wheel들도 함께 소멸
// Java의 GC가 자동으로 처리하지만 개념적으로는 강한 결합
}
class Engine {
private String type;
private int horsepower;
public Engine(String type, int horsepower) {
this.type = type;
this.horsepower = horsepower;
}
public void start() {
System.out.println(type + " 엔진 시동");
}
// Engine은 Car 없이는 의미가 없음
}
class Wheel {
private int size;
public Wheel(int size) {
this.size = size;
}
// Wheel도 특정 Car의 부품으로만 존재
}
🔍 특징
- 생명주기 종속성: 부분 객체가 전체 객체에 완전히 종속
- 강한 결합: 전체와 부분이 밀접하게 연결
- 배타적 소유: 부분 객체는 하나의 전체 객체에만 속함
📊 관계별 비교표
관계 Java 코드 UML 표기 설명 생명주기 예시
일반화 | class B extends A | △ 속이 빈 삼각형 화살표 | 상속 관계 (is-a) | 부모-자식 관계 | Dog → Animal |
연관 | class A { B b; } | —— 실선 | 일반적인 참조 관계 | 독립적 | Student → Course |
집합 | class A { List<B> bs; } | ◇ 속이 빈 다이아몬드 | 약한 포함 관계 (has-a) | 부분 독립적 | Department ◇ Student |
합성 | class A { B b = new B(); } | ◆ 속이 채워진 다이아몬드 | 강한 포함 관계 (has-a) | 부분 종속적 | Car ◆ Engine |
🎯 실제 설계에서의 활용
전자상거래 시스템 예시
// 일반화 관계
abstract class User {
protected String email;
protected String name;
public abstract void login();
}
class Customer extends User { // Customer is-a User
private List<Order> orders;
@Override
public void login() {
System.out.println("고객 로그인");
}
}
class Admin extends User { // Admin is-a User
@Override
public void login() {
System.out.println("관리자 로그인");
}
}
// 합성 관계
class Order {
private String orderId;
private List<OrderItem> items; // Order ◆ OrderItem (강한 포함)
private Payment payment; // Order ◆ Payment (강한 포함)
public Order(String orderId) {
this.orderId = orderId;
this.items = new ArrayList<>();
this.payment = new Payment(); // Order 생성시 Payment도 생성
}
}
class OrderItem {
private Product product;
private int quantity;
// OrderItem은 Order 없이는 존재 의미 없음
}
// 집합 관계
class ShoppingCart {
private List<Product> products; // ShoppingCart ◇ Product (약한 포함)
public void addProduct(Product product) {
products.add(product);
}
public void clear() {
products.clear(); // Product 객체들은 여전히 존재
}
}
UML 다이어그램 구조:
User
△
/ \
Customer Admin
Order ◆————————————> OrderItem
◆————————————> Payment
ShoppingCart ◇————————————> Product
🚨 자주하는 실수와 주의점
1. 상속을 연관으로 표현하는 실수
// 잘못된 생각: "Dog가 Animal을 가지고 있다?"
// 올바른 생각: "Dog는 Animal이다"
class Dog extends Animal { ... }
2. 집합과 합성 구분 실수
// 이것은 집합? 합성?
class Team {
private List<Player> players;
}
판단 기준:
- Team이 해체되면 Player들이 다른 팀으로 이적 가능? → 집합
- Team이 해체되면 Player들도 존재할 수 없음? → 합성
3. 생성자에서의 객체 생성 여부
// 합성 관계의 전형적인 패턴
class House {
private Room livingRoom;
public House() {
this.livingRoom = new Room(); // House 생성시 Room도 생성
}
}
// 집합 관계의 전형적인 패턴
class Company {
private List<Employee> employees;
public Company() {
this.employees = new ArrayList<>(); // 빈 컬렉션만 생성
}
public void hireEmployee(Employee emp) { // 외부에서 생성된 객체 추가
employees.add(emp);
}
}
정리
UML 관계 표기법을 정확히 이해하고 사용하면:
- 설계 의도 명확화: 클래스 간 관계의 성격을 정확히 전달
- 코드 품질 향상: 적절한 관계 선택으로 유지보수성 증대
- 팀 커뮤니케이션 개선: 표준화된 표기법으로 일관된 소통
특히 상속은 일반화, 포함은 집합/합성으로 구분하여 표현하는 것이 핵심입니다. 이러한 표기법을 정확히 사용하면 더욱 명확하고 전문적인 설계 문서를 작성할 수 있습니다! 🎯
반응형