본문 바로가기
카테고리 없음

UML 클래스 관계 표기법 완벽 가이드: 상속부터 포함까지

by silvertogold100 2025. 8. 7.
반응형

객체 지향 설계를 할 때 클래스 간의 관계를 명확하게 표현하는 것은 매우 중요합니다. 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 관계 표기법을 정확히 이해하고 사용하면:

  1. 설계 의도 명확화: 클래스 간 관계의 성격을 정확히 전달
  2. 코드 품질 향상: 적절한 관계 선택으로 유지보수성 증대
  3. 팀 커뮤니케이션 개선: 표준화된 표기법으로 일관된 소통

특히 상속은 일반화, 포함은 집합/합성으로 구분하여 표현하는 것이 핵심입니다. 이러한 표기법을 정확히 사용하면 더욱 명확하고 전문적인 설계 문서를 작성할 수 있습니다! 🎯

반응형