본문 바로가기
Concepts/UML

UML - 클래스 다이어그램 고급 - 연관 클래스

by ocwokocw 2021. 2. 10.

- 이 글은 UML Distilled (마틴 파울러)책을 기반으로 작성하였습니다.

- 연관 클래스(Association class)

연관 클래스는 클래스간 연관 관계에 속성, 오퍼레이션 그리고 다른 기능들을 더해줄 수 있도록 해준다. 어떤 사람(Person)이 회의(Meeting)에 참석(Attendance)하는 다이어그램을 한번 살펴보자.

Person과 Meeting 연관 관계에 Attendance 연관 클래스를 더했다. 또 연관 클래스에는 참석 여부를 가리키는 attentiveness 속성을 더했다. 사실 연관 클래스를 사용하지 않아도 우리는 이 관계를 표현할 수 있다.

연관 클래스가 아닌 완전한 클래스로 변환하여 위와 같은 다중성을 표시해주면 의미가 같아진다. 다중성을 변환한 부분이 좀 헷갈릴 수 있겠다. 

 

Meeting을 1개로 고정해놓고 생각해보자. Attendance 없이 Meeting 과 Person 관계만 생각한다면 Meeting이 1일 때 Person은 2 이상이 될 것이다. 2..* 다중성을 Attendance에 표시해준다. 반대의 경우에도 Person이 1 이라면 Meeting이 여러개일 수 있고, * 다중성을 Meeting 대신 Attendance에 표시해주면 위와 같이 다중성시 표시된다.

 

어차피 기존 표기법으로 연관을 나타낼 수 있는데 연관 클래스라는 개념 자체를 왜 도입했을까? 연관 클래스에는 추가적인 제약사항이 하나 존재한다. 연관에 참여하는 두 객체 사이에 오직 하나의 연관 클래스 인스턴스만이 있을 수 있다.

Company-Contact 연관 관계에서 Role을 연관 클래스로 표기하면 Company는 1개의 Contact에 대해서 1개의 Role만 가질 수 있다. 만약 특수한 제약사항에 의해서 그것이 맞다면 상관이 없다. 하지만 만약 Company 가 동일 Contact 에서 여러가지를 가질 수 있는데 연관 클래스를 이용하여 UML을 그렸다면 이는 잘못표기한것이다. 이런 경우에는 연관 클래스가 아닌 완전한 클래스로 표기해야 한다.

 

Person-Skill 연관 관계에서 숙련도(Competency) 연관 클래스가 추가된 다이어그램을 보자. 한명의 Person이 1개의 작업(Skill)에 대해서 숙련도가 여러개인것은 왠만큼 특수한 모델링이 아니라면 생각하기가 어렵다. 그러므로 1 Person - 1 Skill 연관 에서 1개의 숙련도 연관 클래스 인스턴스만 가질 수 있으므로 이 표기는 적절하다고 할 수 있다. 


- 연관 클래스의 구현

연관 클래스를 구현하는건 생각만큼 단순한 일은 아니다. Person과 Meeting 그리고 Attendance 연관 클래스를 구현한다고 생각해보자.

 

Person의 클라이언트가 Attendance와 Meeting에 대한 정보를 얻을 수 있게 메소드를 제공하는것은 크게 어려운일은 아니다. 이 부분은 일반적인 연관하고는 크게 다를바가 없다.

 

public class Person {

	private List<Attendance> attendances;
	private List<Meeting> meetings;
	
	public List<Attendance> getAttendances(){
		return Collections.unmodifiableList(attendances);
	}

	public List<Meeting> getMeetings() {
		return Collections.unmodifiableList(meetings);
	}
	
}

 

문제는 연관 클래스의 제약사항을 구현할 때 생긴다. 연관 클래스는 연관된 두 객체 사이의 인스턴스가 1개로 제한된다고 했다. Person의 입장에서 Meeting 에 참가하는 오퍼레이션에 이를 적용시켜서 코드를 작성해보자.

 

public class Meeting {

	private String title;

	public Meeting(String title) {
		super();
		this.title = title;
	}

	@Override
	public String toString() {
		return "Meeting [title=" + title + "]";
	}
}

public class Attendance {

	private Person person;
	private Meeting meeting;
	private boolean attentiveness = false;
	
	public Attendance(Person person, Meeting meeting, boolean attentiveness) {
		super();
		this.person = person;
		this.meeting = meeting;
		this.attentiveness = attentiveness;
	}

	public void attendMeeting() {
		this.attentiveness = true;
	}
	
	public void notAttendMeeting() {
		this.attentiveness = false;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Attendance other = (Attendance) obj;
		if (meeting == null) {
			if (other.meeting != null)
				return false;
		} else if (!meeting.equals(other.meeting))
			return false;
		if (person == null) {
			if (other.person != null)
				return false;
		} else if (!person.equals(other.person))
			return false;
		return true;
	}
}

 

위는 Meeting 과 Attendance 클래스이다. Meeting은 이름속성만 가지고있다. Attendance 연관 클래스에서 equals는 Person과 Meeting을 기준으로 판단한다. attentiveness는 참석여부이므로 해당 객체가 고유한 Attendance 객체인지를 판단하는데서는 빼야 한다.

 

public class Person {

	private List<Attendance> attendances;
	private List<Meeting> meetings;
	
	public Person() {
		super();
		this.attendances = new ArrayList<>();
		this.meetings = new ArrayList<>();
	}

	public List<Attendance> getAttendances(){
		return Collections.unmodifiableList(attendances);
	}

	public List<Meeting> getMeetings() {
		return Collections.unmodifiableList(meetings);
	}
	
	public void attendMeeting(Meeting meeting) {
		
		Attendance newAttendance = new Attendance(this, meeting, true);
		
		if(attendances.contains(newAttendance)) {
			System.out.println("The meeting is already appended.: " + meeting);
			return;
		}
		
		attendances.add(newAttendance);
	}
}

 

이제 Person 클래스에서 미팅 참석(attendMeeting) 오퍼레이션을 살펴보자. Person 이 새로운 Meeting을 참석하려고 할 때 이미 해당 회의에 참석되었는지를 contains를 통해 판단하고 있다. 이렇게 함으로써 "Person과 Meeting 쌍 사이에는 1개의 Attendance 객체만 존재할 수 있다." 연관 클래스 제약조건을 지킬 수 있게 해주었다.


- <<temporal>> 키워드

종종 이력 정보를 가진 구조를 구현할때가 있다. 만약 어떤 Person 이 Company 에서 일할 때, 특정 기간동안 해당 회사에서 일한다면 아래와 같이 다이어그램을 표현할 수 있다.

솔직하게 내가 프로젝트에서 이런 상황에 맞이했다면 그냥 이렇게 구현했을수도 있을것 같다. 마틴 파울러는 이렇게 추가로 클래스나 연관 클래스를 만드는것이 모델을 이해하기 어려운 상황으로 만들 수도 있다고 생각하고 있다.

이런 경우 Employeement 를 "임시" 정보라고 규정하고, UML의 정식 키워드는 아니지만 <<temporal>> 키워드를 연관에 사용할수도 있다. 물론 구현은 클래스를 따로 빼지 않고, Person 클래스에서 Date 를 기준으로 Company 정보를 찾아오거나 퇴사를 할 수 있는 오퍼레이션들을 제공해야 할것이다.

 

댓글