프로그래밍중 구조를 설계하는 업무를 진행하다보면 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
시스템의 클래스와 클래스 간의 관계를 보여준다.
클래스 다이어그램은 객체지향 설계에서 중요한 역할을 한다.
유즈케이스 다이어그램
Use Case Diagram
시스템의 기능적 요구사항과
사용자와 시스템 간의 상호작용을 나타낸다.
시퀀스 다이어그램
Sequence Diagram
시스템의 상호작용을 시간 순서대로 보여주며,
객체 간의 메시지 흐름을 보여준다
상태 다이어그램
State Diagram
객체의 상태와 상태 전이를 보여주고,
객체가 특정 조건에서 어떤 동작을 하는지 나타낸다.
활동 다이어그램
Activity 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으로 표현할 수 있다.

Monster 클래스의 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으로 표현할 수 있다.

interface와 abstract class를 표현하는 방법



🔶 Relationships between classes

Class diagram의 주 목적은 클래스간의 관계를 한눈에 쉽게 보고 의존 관계를 파악하는 것에 있다.

그렇기 때문에 다이어 그램에서 가장 중요한것이 클래스간의 관계이다.

Class Diagram에서 어떤 두 클래스가 관계가 있을 땐 화살표로 두 클래스를 연결하는 것으로 표현한다.

다만, Class와 Class(혹은 Interface)사이에는 여러가지 관계가 있는데, 각 관계를 쉽게 도식화하기 위해

다음과 같은 화살표를 사용하고 있다.


관계 UML 표기
Generalization 일반화 관계
Realization 실체화 관계
Composition 합성 관계
Aggregation 집합 관계
Association 연관 관계
Dependency 의존 관계


이제 이런 다양한 종류의 관계에 대해 알아보고, 어떤 화살표를 언제 사용할 수 있는지 알아보도록 한다.



🔷 Arrow Direction

다만, 화살표의 종류를 알아보기 전에 한가지 알고 넘어가야 할 것이 있다.

바로 어느 방향으로 화살표를 써야 하는가? 이다.

조금만 생각해보면 화살표는 방향을 가지고 있는 것을 알 수 있다!

그럼, 도대체 어떤 방향으로 화살표를 그려야 하는가?

예를 들어, 아래와 같은 경우가 있다고 생각해보자.

public class Animal
{
    // Something for Animal class
}
public class Cat : Animal
{
    // Something for Cat class
}


조금 있다가 후술하겠지만, 두 Class간 상속 관계를 표현하기 위해서는 일반화 관계를 표현해서 사용한다.

그렇다면, 다음 2가지 Class Diagram중 어떤 diagram이 위 코드를 표현한 것일까?

두 가지 Diagram 중, 어떤 diagram이 정답일까


전에 NHN의 민석책임님이 이에 대한 아주 간단하고 명확한 솔루션을 말씀해주신적이 있다.

UML에서 화살표의 방향은, class가 다른 class를 사용하려고 갈고리를 던진다고 생각하면 쉽다 라는 것이다.

위 코드에서 CatAnimal class들 중, 누가 누구를 참고하고 있는가?


AnimalCat을 몰라도 되지만, CatAnimal이 없으면 상속을 받을 수 없다.

즉, Cat이 상속을 받기 위해 Animal을 참고하고있으므로, CatAnimal로 화살표를 그리면 된다.

Animal을 사용하기 위해 화살표를 던지는 Cat

상속이라는 특징상, 부모 - 자식 이라는 컨셉으로 인해 가계도를 떠올려

몇몇 사람들은 왼쪽이라고 착각하기 쉽지만,

UML에서는 오른쪽이 상기 코드를 표현한 class diagram이다.

오른쪽이 정답임


이 점을 항상 유의하고 Class diagram을 작성하도록 & 읽도록 하자



🔷 Generalization (일반화) 관계

Generalization을 표현하는 화살표

일반화 관계를 표시하기 위해서는, 속이 빈 세모로 끝나는 실선 화살표를 사용한다.

일반화 관계란, Super ClassSub class간의 상속 관계를 나타내는데 사용된다.

예를 들어, 다음과 같이 ConsumableItem을 상속받은 HealingPotionManaPotion이 있다고 하자.

public class HealingPotion : ConsumableItem
{
    public GameCharacter TargetCharacter;
    public int HealAmount;

    public override void Consume()
    {
        AddHealthPoint();
    }

    private void AddHealPoint()
    {
        TargetCharacter.characterHp 
                += HealAmount;
    }
}
public class ManaPotion : ConsumableItem
{
    public GameCharacter TargetCharacter;
    public int ManaAmount;

    public override void Consume()
    {
        AddManaPoint();
    }

    private void AddManaPoint()
    {
        TargetCharacter.characterMp
                += ManaAmount;
    }
}


그럼 아래와 같은 Class diagram으로 표현할 수 있다.

Generalization 관계


화살표 방향에 주의 할 것!



🔷 Realization (실체화) 관계

Realization을 표현하는 화살표

일반화 관계를 표시하기 위해서는, 속이 빈 세모로 끝나는 점선 화살표를 사용한다.

일반화 관계란, interfaceclass간의 구현 관계를 나타내는데 사용된다.

예를 들어, 다음과 같이 IGameWeapon interface를 구현한 CrossbowSword가 있다고 하자.

public class Crossbow : IGameWeapon
{
    public void ActivateWeapon()
    {
        Shot();
    }

    private void Shot()
    {
        // DO Something
    }
}
public class Sword : IGameWeapon
{
    public void ActivateWeapon()
    {
        Slash();
    }

    private void Slash()
    {
        // DO Something
    }
}


그럼 아래와 같은 Class diagram으로 표현할 수 있다.

Realization 관계




🔷 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을 표현하는 화살표

Composition 관계를 표시하기 위해서는, 속이 꽉찬 마름모로 시작하고, 얇은 선으로 끝나는 직선 화살표를 사용한다.

TaxiEngine 의 관계는 Composition (구성) 이다.

Egnine의 instance인 carEngineTaxi와 life cycle을 같이 공유하고 있기 때문이다.

public class Taxi
{
    public Taxi(Driver driver)
    {
        this.carEngine = new Engine(); // <- 생성자와 같이 할당
        this.taxiDriver = driver;
    }
}

언젠가 Taxi intance가 생길때 Engine intance도 생성되며,

Taxi의 인스턴스가 사라질 때, Engine instance도 사라질 것이다.

일종의 밀접한 관계이다.

Composition 관계



🔷 Aggregation (집합) 관계

Aggregation을 표현하는 화살표

Aggregation 관계를 표시하기 위해서는, 속이 빈 마름모로 시작하고, 얇은 선으로 끝나는 직선 화살표를 사용한다.

TaxiDriver 의 관계는 Aggregation (집합) 이다.

Driver의 instance taxiDriver는 생성자 매개변수 driver로 전달되어 저장된 값이다.

public class Taxi
{
    public Taxi(Driver driver)
    {
        this.carEngine = new Engine();
        this.taxiDriver = driver;      // <- 외부에서 생성후 할당
    }
}

즉, taxiDriver가 생성된 시점은 Taxi instance가 알 수 없으며

taxiDriverTaxi instance의 Life cycle이 무관하기 때문에 이 관계는 Aggregation으로 볼 수 있다.

이런 특징으로, Aggregation은 느슨한 관계라고 볼 수 있다.

Aggregation 관계



🔷 Association (연관) 관계

Association을 표현하는 화살표

Association 관계를 표시하기 위해서는, 얇은 선으로 끝나는 직선 화살표를 사용한다.

TaxiPassenger 의 관계는 Association (연관) 이다.

Passenger의 instance taxiPassengerPickUpPassenger메서드의 매개변수 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 메서드를 사용할 수도, 안 할 수도 있기 때문에

PassengerTaxiAssociation (연관) 관계에 있지만, Aggregation (집합) 관계는 아닌 것이다.

Association 관계



🔷 Dependency (의존) 관계

Dependency를 표현하는 화살표

Dependency 관계를 표시하기 위해서는, 얇은 선으로 끝나는 점선 화살표를 사용한다.

TaxiCreditCard 의 관계는 Dependency (의존) 이다.

public class Taxi
{
    public int PayByCard(CreditCard card)
    {
        return card.PayForTaxi();    // <- 어딘가에서 생성후 메서드를 통해 전달
    }
}

Dependency 관계 역시 Association 관계처럼 메서드를 통해 호출하는 것으로 보인다.

AssociationDependency 를 구분하는 기준은 instance의 reference가 유지 되느냐 여부이다.

CreditCard의 instance는 PayByCard 메서드 내부에서 사용하고 바로 소멸되기 때문에 이 경우는 Dependency 인 것이다.

Dependency 관계



🔷 자가평가

Taxi class와 그 주변 class들을 class Diagram으로 표현하면 아래와 같다.

추가로 IVehicle interface와 Person class를 넣어보았다.

Taxi class의 Class Digram

본 Post를 정독했으면, 두 class가 추가되었어도 어떤 관계인지 알 수 있게된다.