UML은 객체 지향 프로그래밍의 디자인 패턴을 공부하기 전 필요한 선행 지식입니다. 이 글에선 UML이 뭔지, 디자인 패턴을 공부할 때 UML을 왜 알아야 하는지 알아봅시다. UML은 통합 모델링 언어(Unified Modeling Language)의 줄임말이며, 소프트웨어를 설계할 때 사용하는 언어입니다. 먼저 UML 이름의 뜻부터 자세히 살펴보면,
Unified - 과거 1990년대에는 수많은 객체 지향 소프트웨어 개발 방법론이 있었습니다. 이 많은 방법론들에서 사용하는 모델링 방법/언어를 통합했다는 의미로 추측됩니다.
Modeling - 소프트웨어 모델링은 소프트웨어 설계를 표현하는 방법입니다.
Language - 언어는 의사를 소통하기 위한 수단입니다. UML 역시 소프트웨어 모델을 서로 공유하고 이에 대해 소통하는 수단입니다.
내용
UML은 여러 사람들이 수행하는 소프트웨어 모델링을 위한 작업들을 통합된 방법으로 할 수 있게 해줍니다. 이를 위해 UML은 수많은 다이어그램들을 제공합니다. 이 중에서 디자인 패턴을 공부할 때 가장 필요한 다이어그램은 class diagram, sequence diagram입니다. 다음 두 절에선 다음 2가지 다이어그램에 대한 기본적인 문법을 살펴보겠습니다.
클래스 다이어그램
Class diagram은 시스템의 정적인 구조(static structure)를 그리는 다이어그램입니다. 정적인 구조란 말그대로 실행 중이지 않은 시스템의 객체 구조를 나타냅니다. OOP에서 사용하며 클래스와 인터페이스들간의 정적인 연관 관계(상속, 구현, 조합 등)를 나타낼 때 사용한다고 이해하면 편합니다.
UML은 말그대로 언어이며 여러 사투리가 있을 수 있습니다. 즉, 여기서 설명한 문법이 인터넷 상에 있는 모든 class diagram에 동일하게 적용되지 않을 수 있습니다. 핵심은 소통이라고 생각합니다. UML을 사용해 소통하려는 사람/조직들과 잘 소통된다면 문법은 어느정도 변형될 수 있다고 생각합니다. 아래 내용들은 참고용으로만 알아주세요. 저도 OMG에서 발행하는 UML specification의 내용을 모두 알지 못합니다.
클래스
Class diagram에서 클래스는 이름(name), 속성(attribute), 연산(operation)이라는 3가지 구성요소를 가집니다. 이 3가지를 네모 박스 안에 가로 줄을 그어 구분해놓은 것이 클래스의 표현법입니다.
이름은 보통 굵은 글씨로 적으며 말그대로 클래스의 이름을 뜻합니다.
속성은 클래스의 객체에 속하는 정보, 데이터, 속성 등을 나타냅니다.
연산은 클래스의 객체가 수행할 수 있는 서비스나 액션을 나타냅니다.
자바의 예로 들면, 이름은 클래스의 이름, 속성은 클래스의 멤버 변수, 연산은 클래스의 메소드로 생각할 수 있습니다. 클래스는 아래의 그림처럼 표기하며 인터페이스의 경우 <<interface>>를 이름 위에 적습니다. 혹은 단순한 원으로 인터페이스를 나타낼수도 있습니다.
UML에서 인터페이스는 클래스나 컴포넌트가 반드시 구현해야하는 연산들의 집합입니다.
가시성과 범위
위 클래스 그림에서 속성과 연산의 이름 전에 기호가 붙어있는 것을 볼 수 있습니다. 이는 각각의 가시성을 나타냅니다. 여러 객체 지향 언어에서 제공하는 가시성 public, protected, private 등과 같은 의미를 합니다.
+
Public
-
Private
#
Protected
~
Package
위 4가지만 알아도 디자인 패턴을 공부할 때 큰 어려움이 없습니다.
UML에선 클래스의 이름에 밑줄을 긋거나 기울임으로써 의미를 부여할 수 있습니다.
밑줄을 그으면, 그 클래스는 static class란 뜻입니다.
글자를 기울이면, 그 클래스는 abstract class란 뜻입니다.
연관 관계
클래스 다이어그램은 클래스들의 연관 관계를 나타낼 수 있습니다.
Dependency는 클래스간에 의존 관계가 있을 때 사용합니다. 한 클래스(server)의 변경이 다른 클래스(client)에 영향이 있을 때 두 클래스는 dependency로 나타낼 수 있습니다. 아래 그림처럼 client에서 시작해 server로 그어진 dotted line으로 표현합니다.
Association은 클래스간에 일반적인 연관 관계가 있을 때 사용합니다. 단방향일 수도 있고 양방향일 수도 있습니다. 아래 그림처럼 표현할 수 있습니다.
Dependency vs Association? 처음 이 두 연관 관계를 접하고 클래스 다이어그램을 그리면 그 차이를 확실히 정하기 어려울 수 있습니다. 넓게 인정되는 방식으로는, Association은 한 클래스가 자신의 멤버, 속성 등으로 다른 클래스를 가질 때 사용합니다. Dependency는 한 클래스가 다른 클래스를 연산의 인자로 받거나, 객체화하거나, 연산 중간에 잠깐 사용할 때 사용합니다. 아래 예제를 봅시다.
위 예제에서 Apple은 Price를 멤버로 가지고 있고 연산에서 객체화하기도 하므로 association이면서 dependency 관계입니다. Currency는 연산의 인자로 받으므로 Apple과 dependency 관계를 가집니다.
Aggregation은 클래스간에 "has-a" 관계가 있을 때 사용합니다. 즉, 한 클래스가 다른 클래스를 포함할 때 사용합니다. 아래와 같이 표현합니다.
Composition은 "has-a" 관계가 있을 때 사용하지만 aggregation보단 조금 더 강한 개념입니다. 포함하는 클래스가 포함되는 클래스의 lifetime을 관리할 때 일반적으로 사용합니다. 즉, 포함되는 클래스는 포함하는 클래스에 완전하게 종속됩니다. 아래 그림처럼 채워진 다이아몬드를 사용해 표현합니다.
Association vs Aggregation? Association과 Aggregation은 구분하기 어렵습니다. 이 두 관계를 코드로 구현할 때 딱히 차이점을 가지지 않기 때문입니다. 보통은 둘 다 한 클래스가 다른 클래스의 객체를 가지고 있죠. 이런 모호성 때문에 몇몇 사람들은 aggregation의 사용을 별로 달갑게여기지 않습니다. 유명한 개발자인 마틴 파울러도 그의 저서인 <<UML Distilled>>에서 aggregation를 무시하도록 권하고 있습니다. 그렇기 때문에 만약 자신이 aggregation을 쓰거나 이를 쓰는 조직에서 일할 땐 이 연관관계의 의미를 명확하게 정의할 필요가 있습니다.
Generalization은 상속 관계를 나타냅니다. 아래와 같이 표현합니다.
Realization은 인터페이스를 구현할 때 사용합니다. 아래와 같이 표현합니다.
Multiplicity
각 연관관계에는 옵션으로 명시할 수 있는 표시인 multiplicity 있습니다. Multiplicity는 그 연관관계에 참여하는 객체의 수입니다. 예를 들어, 위 예제로 사용했던 교수-조교 관계와 사람-심장 관계에 multiplicity를 추가해보겠습니다.
위 숫자를 말로 풀어쓰면,
교수는 한 명 이상(1..*)의 조교를 가진다.
조교는 한 명의 교수를 가진다.
사람은 한 개의 심장을 가진다.
심장은 한 명의 사람을 가진다.
이다. 대충 감을 잡았을 것입니다. 많이 보다보면 자연스럽게 읽을 수 있을 것입니다. 주로 쓰는 multiplicity는 아래와 같습니다.
* : 0 ~ ∞
0..* : 0 ~ ∞
1..* : 1 ~ ∞
1 : 1
0..n : 0 ~ n
n..m : n ~ m
시퀀스 다이어그램
Sequence diagram은 소프트웨어의 동적인 면을 나타냅니다. 이 다이어그램을 통해 객체들이 어떻게 상호작용하는지 표현할 수 있습니다.
요소들
시퀀스 다이어그램은 Lifeline, message, return value로 구성됩니다. 다른 요소들도 있지만 이 3가지만 알아도 시퀀스 다이어그램을 읽고 쓰는데 큰 문제가 없습니다.
Lifeline은 상호작용에 참여하는 객체들을 의미합니다. <객체 이름>:<클래스 이름>이 쓰여진 사각형과 밑으로 이어진 점선으로 구성됩니다. 객체 이름을 생략하기도 합니다.
객체들 간에는 message를 주고받을 수 있습니다. 메시지는 동기 메시지와 비동기 메시지로 나뉩니다. 동기 메시지는 객체가 응답할 때까지 기다리며 비동기 메시지는 메시지를 보낸 후 응답에 상관없이 자기 일을 합니다. 두 메시지는 아래와 같이 표현합니다. 메시지를 보낸 객체는 반환 메시지를 받을 수 있습니다.
위 그림에서 보듯이 객체는 자기 자신에게도 메시지를 보낼 수 있습니다.
대부분의 상호작용은 위 요소들만으로 나타낼 수 있지만 guard를 이용해 분기, 반복 등을 처리할 수 있습니다.
위 예제처럼 나타내며 loop뿐만 아니라 opt(if), alt(if)-else, break par(parallel) 등의 가드를 사용할 수 있습니다. 다만 가드의 과도한 사용은 시퀀스 다이어그램을 보기 어렵게 만드므로 꼭 필요한 경우에만 쓰는걸 추천드립니다.
마무리
디자인 패턴은 자주 나타나는 문제에 대한 일반적이고 재사용가능한 해결책이라고 할 수 있습니다. 객체 지향 프로그래밍에서 자주 나타나는 문제는 당연히 클래스들간의 의존성이나 결합도 등, 즉 클래스간의 연관 관계에서 자주 발생합니다. 그렇기 때문에 객체 지향 프로그래밍의 디자인 패턴은 주로 UML을 이용해 문제 상황과 해결책을 표현합니다. 이 말이 잘 와닿지 않을 수 있습니다. 일단 위 내용은 이런게 있구나라는 생각으로 읽은 다음 앞으로 쓸 디자인 패턴 글을 읽어보세요! 디자인 패턴 글을 읽을수록 자연스럽게 UML에 익숙해질 것입니다.