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가 추가되었어도 어떤 관계인지 알 수 있게된다.