UML 다이어그램 - 클래스 다이어그램 정리
클래스 다이어그램에 대해 공부한 내용을 정리해본다.
2024.04.19
프로그래밍중 구조를 설계하는 업무를 진행하다보면 UML Diagram을 사용해야 하는 경우가 많다.
이번 포스트에서는 다양한 UML Diagram들 중, 어떤 방식으로 클래스 다이어그램을 사용해야하는지 공부한 내용을 정리해본다.
💠 Unified Modeling Language
UML이란, 시스템을 모델링하기 위한 표준화된 언어로서, 누구나 쉽게 해당 시스템을 이해할 수 있도록 하는 모델링 언어이다.
여기서 중요한 점은 이 UML이라는 것이 언어라는 점인데,
이 언어를 통해 시스템 및 아키텍처의 구조를 가시화하는 Diagram을 쉽게 만들수 있다고 한다.
이 UML Diagram을 사용하면 다음과 같은 장점을 얻을 수 있을 것으로 기대한다.
🔸 시각적 표현
UML은 시스템을 시각적으로 표현할 수 있는 강력한 도구로서, 다이어그램을 사용하여 복잡한 시스템을 간결하고 명확하게 표현할 수 있다.
🔸 표준화된 언어
UML은 표준화된 언어로써 소프트웨어 개발자들 간의 통일된 이해를 도와준다. 이는 의사소통을 향상시키고 협업을 용이하게 한다.
🔸 의사소통 도구
UML은 개발자, 디자이너, 관리자 등 각종 업무 관계자간의 의사소통 도구로 활용되어 시스템의 구조, 동작 등을 이해하기 쉽게 표현하여 소통에 도움을 준다.
누군가는 UML의 장점을 개발 용이성이라고 하는데, 사용해보면 알겠지만 용이함과는 거리가 멀다.
왜냐하면, 이 UML을 배우는데에는 새로운 언어 하나를 배우는 수준의 수고가 필요할 정도로 입문 허들이 높기 떄문이다.
그리고 바로 이 이유가 요즘에는 UML이라는 언어 자체가 산업에서 거의 사용되지 않는이유가 되었다.
다만, UML이라는 언어 자체는 사장되었을지 몰라도 UML Diagram은 도식이나 학습을 위해 굉장히 빈번하게 사용되고 있기 때문에
이 UML Diagram에 대한 내용을 알고 있는것이 좋다고 생각해 포스트를 작성하게 되었다.
UML이 지원하는 Diagram은 다양한 종류가 있지만, 개인적으로 자주 사용할것 같은 Diagram을 추린다면 다음과 같은 종류가 있다.
오늘은 이들 중, Class Diagram에 대해서 알아본다.
💠 Class Diagaram
Class Diagram이란, 시스템에서 사용되는 객체 타입을 정의하고, 그 객체들간 존재하는 관계들을 다양한 방식으로 표현한 다이어그램이다.
🔶 Class & Interface
🔷 Class
Class Diagram에서 Class를 표현할 땐, 3개의 구역으로 나누어 클래스의 이름, 필드, 메서드를 표기한다.
필드와 메서드는 옵션으로 생략이 가능하다.
예를 들어, 아래와 같은 클래스가 있다고 하자.
public class Monster
{
public string MonsterName;
private int monsterHp;
public void Hit(int damage)
{
// 데미지 처리
}
public List<GameItem> DropItems()
{
// 드랍하는 아이템 처리
}
private private OnSpawned()
{
// 생성될 때 처리
}
}
그럼 위 Monster class는 아래 Class diagram으로 표현할 수 있다.
🔷 Interface & Abstract Class
또한 interface나 abstract class의 경우엔 <<interface>>와 <<abstract>>와 같은 키워드를 추가해 표현한다.
마찬가지로 예를 들면, 아래와 같은 IGameWeapon이라는 interface와 ConsumableItem이라는 abstarct class가 있다고 하자.
public interface IGameWeapon
{
public void ActivateWeapon();
}
public abstract class ConsumableItem
{
public void Consume();
}
그럼 아래와 같은 Class diagram으로 표현할 수 있다.
🔶 Relationships between classes
Class diagram의 주 목적은 클래스간의 관계를 한눈에 쉽게 보고 의존 관계를 파악하는 것에 있다.
그렇기 때문에 다이어 그램에서 가장 중요한것이 클래스간의 관계이다.
Class Diagram에서 어떤 두 클래스가 관계가 있을 땐 화살표로 두 클래스를 연결하는 것으로 표현한다.
다만, Class와 Class(혹은 Interface)사이에는 여러가지 관계가 있는데, 각 관계를 쉽게 도식화하기 위해
다음과 같은 화살표를 사용하고 있다.
| 관계 | UML 표기 | |
|---|---|---|
| Generalization | 일반화 관계 | ![]() |
| Realization | 실체화 관계 | ![]() |
| Composition | 합성 관계 | ![]() |
| Aggregation | 집합 관계 | ![]() |
| Association | 연관 관계 | ![]() |
| Dependency | 의존 관계 | ![]() |
이제 이런 다양한 종류의 관계에 대해 알아보고, 어떤 화살표를 언제 사용할 수 있는지 알아보도록 한다.
🔷 Arrow Direction
다만, 화살표의 종류를 알아보기 전에 한가지 알고 넘어가야 할 것이 있다.
바로 어느 방향으로 화살표를 써야 하는가? 이다.
조금만 생각해보면 화살표는 방향을 가지고 있는 것을 알 수 있다!
그럼, 도대체 어떤 방향으로 화살표를 그려야 하는가?
예를 들어, 아래와 같은 경우가 있다고 생각해보자.
|
|
조금 있다가 후술하겠지만, 두 Class간 상속 관계를 표현하기 위해서는 일반화 관계를 표현해서 사용한다.
그렇다면, 다음 2가지 Class Diagram중 어떤 diagram이 위 코드를 표현한 것일까?
전에 NHN의 민석책임님이 이에 대한 아주 간단하고 명확한 솔루션을 말씀해주신적이 있다.
UML에서 화살표의 방향은, class가 다른 class를 사용하려고 갈고리를 던진다고 생각하면 쉽다 라는 것이다.
위 코드에서 Cat과 Animal class들 중, 누가 누구를 참고하고 있는가?
Animal은 Cat을 몰라도 되지만, Cat은 Animal이 없으면 상속을 받을 수 없다.
즉, Cat이 상속을 받기 위해 Animal을 참고하고있으므로, Cat → Animal로 화살표를 그리면 된다.
상속이라는 특징상, 부모 - 자식 이라는 컨셉으로 인해 가계도를 떠올려
몇몇 사람들은 왼쪽이라고 착각하기 쉽지만,
UML에서는 오른쪽이 상기 코드를 표현한 class diagram이다.
이 점을 항상 유의하고 Class diagram을 작성하도록 & 읽도록 하자
🔷 Generalization (일반화) 관계
일반화 관계를 표시하기 위해서는, 속이 빈 세모로 끝나는 실선 화살표를 사용한다.
일반화 관계란, Super Class와 Sub class간의 상속 관계를 나타내는데 사용된다.
예를 들어, 다음과 같이 ConsumableItem을 상속받은 HealingPotion과 ManaPotion이 있다고 하자.
|
|
그럼 아래와 같은 Class diagram으로 표현할 수 있다.
화살표 방향에 주의 할 것!
🔷 Realization (실체화) 관계
일반화 관계를 표시하기 위해서는, 속이 빈 세모로 끝나는 점선 화살표를 사용한다.
일반화 관계란, interface와 class간의 구현 관계를 나타내는데 사용된다.
예를 들어, 다음과 같이 IGameWeapon interface를 구현한 Crossbow과 Sword가 있다고 하자.
|
|
그럼 아래와 같은 Class diagram으로 표현할 수 있다.
🔷 Taxi class 예제
이제 Composition 관계, Aggregation 관계, Association 관계, Dependency 관계에 대해 알아볼 예정이다.
이 4가지 관계는 모두 한 class에서 다른 class를 참조하고 있음을 나타내는데,
아래에 있는 Taxi class의 예시를 통해 보다 명확히 이해해보도록 한다.
public class Taxi : Vehicle
{
private Engine carEngine; // 택시 엔진
private Driver taxiDriver // 택시를 운전할 드라이버
private Passenger taxiPassenger; // 택시를 탈 손님
//============================================================
// Taxi class 생성자
//============================================================
public Taxi(Driver driver)
{
this.carEngine = new Engine();
this.taxiDriver = driver;
}
//============================================================
// 승객을 태우는 메서드
//============================================================
public void PickUpPassenger(Passenger passenger)
{
this.taxiPassenger = passenger;
}
//============================================================
// 카드를 전달받아 그 카드에서 Taxi요금을 받아오는 메서드
//============================================================
public int PayByCard(CreditCard card)
{
return card.PayForTaxi();
}
}
🔷 Composition (구성) 관계
Composition 관계를 표시하기 위해서는, 속이 꽉찬 마름모로 시작하고, 얇은 선으로 끝나는 직선 화살표를 사용한다.
Taxi와 Engine 의 관계는 Composition (구성) 이다.
Egnine의 instance인 carEngine은 Taxi와 life cycle을 같이 공유하고 있기 때문이다.
public class Taxi
{
public Taxi(Driver driver)
{
this.carEngine = new Engine(); // <- 생성자와 같이 할당
this.taxiDriver = driver;
}
}
언젠가 Taxi intance가 생길때 Engine intance도 생성되며,
그 Taxi의 인스턴스가 사라질 때, Engine instance도 사라질 것이다.
일종의 밀접한 관계이다.
🔷 Aggregation (집합) 관계
Aggregation 관계를 표시하기 위해서는, 속이 빈 마름모로 시작하고, 얇은 선으로 끝나는 직선 화살표를 사용한다.
Taxi와 Driver 의 관계는 Aggregation (집합) 이다.
Driver의 instance taxiDriver는 생성자 매개변수 driver로 전달되어 저장된 값이다.
public class Taxi
{
public Taxi(Driver driver)
{
this.carEngine = new Engine();
this.taxiDriver = driver; // <- 외부에서 생성후 할당
}
}
즉, taxiDriver가 생성된 시점은 Taxi instance가 알 수 없으며
taxiDriver와 Taxi instance의 Life cycle이 무관하기 때문에 이 관계는 Aggregation으로 볼 수 있다.
이런 특징으로, Aggregation은 느슨한 관계라고 볼 수 있다.
🔷 Association (연관) 관계
Association 관계를 표시하기 위해서는, 얇은 선으로 끝나는 직선 화살표를 사용한다.
Taxi와 Passenger 의 관계는 Association (연관) 이다.
Passenger의 instance taxiPassenger은 PickUpPassenger메서드의 매개변수 passenger로 전달되어 저장된 값이다.
public class Taxi
{
public void PickUpPassenger(Passenger passenger)
{
this.taxiPassenger = passenger; // <- 어딘가에서 생성후 메서드를 통해 할당
}
}
Aggregation 과 마찬가지로 taxiPassenger 의 life cycle과 Taxi instance의 life cycle은 서로 연관이 없다.
하지만, Aggregation 과 차이는 Taxi instance 가 Passenger 의 instance를
가질 수도 있고 안 가질 수도 있다는 것이다.
Taxi instance를 사용하는 곳에서 PickUpPassenger 메서드를 사용할 수도, 안 할 수도 있기 때문에
Passenger와 Taxi는 Association (연관) 관계에 있지만, Aggregation (집합) 관계는 아닌 것이다.
🔷 Dependency (의존) 관계
Dependency 관계를 표시하기 위해서는, 얇은 선으로 끝나는 점선 화살표를 사용한다.
Taxi와 CreditCard 의 관계는 Dependency (의존) 이다.
public class Taxi
{
public int PayByCard(CreditCard card)
{
return card.PayForTaxi(); // <- 어딘가에서 생성후 메서드를 통해 전달
}
}
Dependency 관계 역시 Association 관계처럼 메서드를 통해 호출하는 것으로 보인다.
Association 과 Dependency 를 구분하는 기준은 instance의 reference가 유지 되느냐 여부이다.
CreditCard의 instance는 PayByCard 메서드 내부에서 사용하고 바로 소멸되기 때문에 이 경우는 Dependency 인 것이다.
🔷 자가평가
위 Taxi class와 그 주변 class들을 class Diagram으로 표현하면 아래와 같다.
추가로 IVehicle interface와 Person class를 넣어보았다.
본 Post를 정독했으면, 두 class가 추가되었어도 어떤 관계인지 알 수 있게된다.




