UML(Unified Modeling Language, 통합 모델링 언어)은 소프트웨어 시스템을 만들 때 아이디어를 그림으로 표현하고, 다른 사람들과 소통하며, 문서로 남기기 위한 표준화된 약속입니다.
UML에는 다양한 다이어그램이 있는데, 그중에서도
클래스 다이어그램(Class Diagram)
은 시스템을 구성하는 가장 기본적인 단위인
클래스(Class)
들과, 이 클래스들 사이의 다양한
관계(Relationship)
를 정적으로 보여주는 데 중점을 둡니다.
UML 클래스 다이어그램
쉽게 말해, 우리가 만들려는 소프트웨어에 어떤 종류의 부품들(클래스들)이 필요하고, 그 부품들이 서로 어떻게 연결되어 있으며, 각 부품은 어떤 특징(속성)과 기능(메서드)을 가지고 있는지를 한눈에 보여주는 상세한 지도와 같습니다.
특히 객체 지향 프로그래밍(Object-Oriented Programming, OOP)의 핵심 개념인 캡슐화, 상속, 다형성 등을 시각적으로 표현하고 이해하는 데 매우 효과적입니다.
을 나타냅니다.
부모 클래스(슈퍼클래스, Superclass)가 가진 속성과 메서드를 자식 클래스(서브클래스, Subclass)가 물려받아 사용하거나 확장하는 관계입니다.
"자식 클래스는 부모 클래스의 한 종류이다 (is-a kind of)"로 설명될 수 있습니다. (예: '스마트폰'은 '전자기기'의 한 종류이다)
표현
: 자식 클래스에서 부모 클래스 쪽으로 향하는, 속이 빈 삼각형 화살표 머리(△)를 가진 실선을 사용합니다.
일반화 관계
Java 예시
// 부모 클래스 (슈퍼클래스)abstract class Shape { // 추상 클래스로 정의하여 직접 객체 생성 방지 가능 protected String color; public Shape(String color) { this.color = color; } public abstract double getArea(); // 자식 클래스에서 반드시 구현해야 하는 추상 메서드 public void displayColor() { System.out.println("Color: " + this.color); }}// 자식 클래스 (서브클래스)class Circle extends Shape { private double radius; public Circle(String color, double radius) { super(color); // 부모 클래스의 생성자 호출 this.radius = radius; } @Override // 부모의 추상 메서드 구현 public double getArea() { return Math.PI * radius * radius; }}class Rectangle extends Shape { private double width; private double height; public Rectangle(String color, double width, double height) { super(color); this.width = width; this.height = height; } @Override public double getArea() { return width * height; }}
위 예에서 Circle과 Rectangle은 Shape을 상속받습니다.
다이어그램에서는 Circle에서 Shape으로, Rectangle에서 Shape으로 각각 일반화 관계 화살표를 그립니다.
Shape 클래스 이름은
연관 관계는 두 개 이상의 클래스 객체들이 서로 의미 있는 연결을 가지고 있음을 나타냅니다.
한 클래스의 인스턴스가 다른 클래스의 인스턴스를 참조하거나, 메시지를 주고받으며 상호작용하는 경우입니다.
(예: 학생(Student)과 교수(Professor)는 서로 연관될 수 있습니다. 한 교수는 여러 학생을 지도할 수 있습니다.)
표현
일반적으로 두 클래스 사이에 실선을 그어 표현합니다.
역할 이름 (Role Name)
관계에서 각 클래스가 맡는 역할을 선의 끝에 명시할 수 있습니다. (예: 학생 - 수강생, 교수 - 지도교수)
다중성 (Multiplicity)
관계에 참여하는 객체의 수를 나타냅니다. 선의 양쪽 끝에 숫자로 표시합니다.
1 : 정확히 하나
0..1 : 없거나 또는 하나
* 또는 0..* : 0개 이상 (여러 개)
1..* : 1개 이상
m..n : m개에서 n개 사이 (예: 2..4)
방향성 (Navigability)
화살표를 사용하여 한쪽 클래스에서 다른 쪽 클래스로만 참조 가능한 단방향 관계(예: A -> B는 A가 B를 알지만, B는 A를 모름)인지, 양쪽 모두 참조 가능한 양방향 관계인지(화살표 없거나 양쪽 화살표) 명시할 수 있습니다.
단방향 연관
양방향 연관
Java 예시
고객이 자신의 주문 목록을 아는 단방향 연관
import java.util.List;import java.util.ArrayList;class Order { private String orderId; // ... 다른 주문 관련 속성들 public Order(String orderId) { this.orderId = orderId; } public String getOrderId() { return orderId; }}class Customer { private String customerId; private String name; private List<Order> orders; // 고객은 여러 주문(Order)을 가질 수 있음 (참조) public Customer(String customerId, String name) { this.customerId = customerId; this.name = name; this.orders = new ArrayList<>(); } public void addOrder(Order order) { this.orders.add(order); } public List<Order> getOrders() { return this.orders; }}
이 경우 Customer 클래스는 Order 객체들의 리스트를 멤버 변수(orders)로 가지고 있습니다.
다이어그램에서는 Customer에서 Order로 향하는 화살표를 그리고, Customer 쪽 끝에는 1, Order 쪽 끝에는 0..* (또는 *)를 표시하여 다중성을 나타냅니다.
합성 관계도 '전체(Whole)'와 '부분(Part)' 간의 "has-a" 관계를 나타내지만, 집합 관계보다 훨씬 강한 소유 관계입니다.
부분 객체는 전적으로 전체 객체에 속하며, 전체 객체가 생성될 때 부분 객체가 함께 생성되거나 관리되고, 전체 객체가 소멸되면 부분 객체도 함께 소멸되는
생명주기 의존성을 가집니다.
(예: '자동차(Car)'와 '엔진(Engine)' 관계에서 자동차가 폐차되면 엔진도 함께 폐기됩니다. 엔진은 자동차 없이는 독립적인 의미를 갖기 어렵습니다.)
표현
: 전체 클래스 쪽에 속이 채워진 검은색 마름모(◆)를 그리고, 이 마름모에서 부분 클래스로 실선을 연결합니다.
합성 관계
Java 예시:
// 부분(Part) 클래스 - 주로 전체 클래스 내부에서만 사용됨class Heart { private int bpm; public Heart() { this.bpm = 70; } // 기본 심박수 public void pump() { System.out.println("Heart is pumping at " + bpm + " bpm."); }}// 전체(Whole) 클래스class Person { private String name; private Heart heart; // 사람은 심장을 "소유한다" (강한 결합) public Person(String name) { this.name = name; this.heart = new Heart(); // Person 객체 생성 시 Heart 객체도 함께 생성 } public void live() { System.out.print(name + " is living. "); heart.pump(); } // Person 객체가 소멸되면 (GC 대상이 되면), heart 객체도 함께 소멸 대상이 됨}// 사용 예시Person alice = new Person("Alice");alice.live();// alice 객체가 사라지면, 그 안에 있던 heart 객체도 더 이상 접근 불가능하고 GC 대상이 됨
하는, 가장 약한 형태의 관계입니다.
주로 어떤 클래스의 메서드가 실행될 때 다른 클래스를 매개변수로 받거나, 메서드 내에서 다른 클래스의 객체를 지역 변수로 생성하여 잠깐 사용하는 경우에 나타납니다.
영구적인 연결이 아니라 특정 작업을 수행하는 동안에만 필요한 관계입니다. (예: OrderProcessor가 주문을 처리할 때 EmailService를 사용하여 고객에게 알림 메일을 보내는 경우)
표현
: 사용하는 클래스에서 사용되는 클래스 쪽으로 향하는 점선 화살표(---->)를 그립니다.
의존 관계
Java 예시
// 사용되는 클래스class Logger { public void logMessage(String message) { System.out.println("LOG: " + message); }}// 사용하는 클래스class DataProcessor { public void processData(String data, Logger logger) { // Logger 객체를 매개변수로 받아 사용 // 데이터 처리 로직... System.out.println("Processing data: " + data); logger.logMessage("Data processing complete for: " + data); // Logger의 기능 사용 (의존) }}// 사용 예시DataProcessor processor = new DataProcessor();Logger consoleLogger = new Logger();processor.processData("SampleData123", consoleLogger);// DataProcessor는 processData 메서드 실행 중에만 Logger에 의존합니다.
DataProcessor의 processData 메서드는 Logger 타입의 객체를 매개변수로 받아 사용하므로, DataProcessor는 Logger에 의존합니다.
오늘은 UML 클래스 다이어그램을 사용하여 객체 지향 소프트웨어의 정적인 구조를 설계하는 방법에 대해 Java 예제와 함께 자세히 알아보았습니다.
클래스의 기본 구성 요소인 이름, 속성, 메서드를 정의하고, 클래스들 간의 다양한 관계(일반화, 연관, 집합, 합성, 의존)를 표현하는 방법을 익혔습니다.
다음 시간에는 시스템 내 객체들이 특정 작업을 수행하기 위해 시간의 흐름에 따라 어떻게 메시지를 주고받으며 상호작용하는지 보여주는