Notice
Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

DeFacto-Standard IT

연관관계 본문

Design Pattern/References

연관관계

defacto standard 2017. 10. 28. 03:46

 

연관 관계

 

연관된 클래스 사이에 실선을 그어 표시.

'교수(Professor 클래스)가 학생(Student 클래스)을 상담한다'라는 사실은 다음과 같이 나타낸다.

두 클래스 사이의 연관 관계가 명확한 경우에는 연관 관계 이름('상담한다')을 사용하지 않아도 된다.

 

한 클래스가 다른 클래스와 연관 관계를 가지면 각 클래스의 객체는 해당 연관 관계에서 어떤 역할을 수행한다.

이러한 역할은 클래스 바로 옆 연관 관계를 나타내는 선 가까이에 적을 수 있다.

Professor 객체들은 조언자(advisor 속성)의 역할을,

Strudent 객체들은 피조언자(student 속성)의 역할을 '상담한다' 라는 연관 관계에서 담당한다. 역할 이름은 실제 프로그램을 구현할 때 연관된 클래스의 객체들이 서로를 참조할 수 있는 속성의 이름으로 활용할 수 있다.

*연관 관계의 역할 이름은 연관된 클래스의 객체들이 서로 참조할 수 있는 속성의 이름으로 활용할 수 있다.


다음 클래스 다이어그램을 코드로 작성을 한다.

 

public class Person {
private Phone[] phones;

public Person() {
phones = new Phone[2];
}

public Phone getPhone(int i) {
if (i == 0 || i == 1)
return phones[i] // i = 0이면 집 전화, i = 1이면 사무실 전화

return null;
}

}

이 코드는 집 전화와 사무실 전화를 배열의 인덱스를 통해 구분해야 하므로 매우 불편하다. 이런 경우는 전화기의 역할을 구분해서 사용하면 해결할 수 있다.

 

@Getter
@Setter
public class Person {
private Phone homePhone;
private Phone officePhone;
}

이 코드는 집 전화와 사무실 전화 각각에 참조가 이루어지므로 setter와 getter 메서드로 상황에 맞게 원하는 해당 전화기를 사용할 수 있다.


'상담한다' 연관 관계는 양방향 연관 관계다. 앙방향 연관 관계는 두 클래스를 연결한 선에 화살표를 사용하지 않는다. 즉, 두 클래스의 객체들이 서로의 존재를 인식한다는 의미이다.

public class Professor {
private Student student;

public void setStudent(Student student) {
this.student = student;
student.setAdvisor(this);
}

public void advise() {
student.advise("상담 내용은 여기에...");
}
}

public class Student {
private Professor advisor;

public void setAdvisor(Professor advisor) {
this.advisor = advisor;
}

public void advise(String msg) {
System.out.println(msg);
}
}

public class Main {
public static void main(String[] args) {
Professor hongGilDong = new Professor();
Student manSup = new Student();

hongGilDong.setStudent(manSup);
hongGilDong.advise();
}
}



이 코드의 연관 관계는 양방향 연관 관계이므로 Professor 클래스 객체에서 Student 클래스 객체를 참조할 수 있는 속성(student)이 있고 Student 클래스 객체에서 Professor 클래스 객체를 참조할 수 있는 속성(advisor)이 있다.

또한 이 속성의 이름이 역할 이름을 활용한 것임을 알 수 있다.


두 클래스 사이의 연관 관계를 나타내는 선에 아무런 숫자가 없으면 연관 관계가 일대일 관계임을 나타낸다.

즉, 교수 한명에 학생 한 명만 연관되어 있다. 실제로 학생은 각각은 한 명의 교수와만 연관되어 있지만 일반적으로 교수 한 명은 매우 많은 학생을 상담한다.

 

이를 나타내기 위해 연관된 객체(Student 객체) 수를 연관된 Professor 클래스와 연결한 선 부근에 명시한다.

이를 다중성(multiplicity)이라고 한다.

다중성 표기

의미

1

엄밀하게 1

*, 0..*

0 이상

1..*

1 이상

0..1

0 또는 1

2..5

2 또는 3 또는 4 또는 5

1, 2, 6

1 또는 2 또는 6

1, 3..5

1 또는 3 또는 4 또는 5

 

'1'이 표시되어 있으면 1개의 객체가 연관되어 있다는 뜻

'*'는 0개 이상의 객체가 연관되어 있다는 뜻.

또한 객체 개수의 범위를 나타낼 수 있다.

'1..*'는 1개 이상의 객체가, '0..1'은 없거나 1개의 객체가 연관되어 있다는 뜻.

특정 숫자를 콤마로 분리해 표시하면 해당 숫자만큼의 객체가 연관 되어 있음을 나타낸다.

'1, 2, 6'은 1개 또는 2개 또는 6개의 객체가 연관되어 있음을 의미한다.


다음은 한 교수에 여러 학생이 연관되어 있다는 사실을 나타낸다. 그러나 여전히 한 학생은 한 교수와만 연관을 맺을 수 있다.

 

연관 관계는 방향성을 가질 수 있다.

예를들면 다음 그림의 '수강하다' 연관 관계는 Student 클래스에서 Course 클래스로 향하도록 되어 있다.

이는 학생(Student 객체)은 자신이 수강하는 과목(Course 객체)을 알지만 과목은 자신을 수강하는 학생들의 존재를 모른다는 사실을 의미.

이렇게 한쪽으로만 방향성이 있는 연관 관계를 단방향 연관 관계라 한다.

따라서 Student 객체는 Course 객체(들)를 참조할 수 있도록 구성해야 하지만 Course 클래스는 Student 객체를 참조할 속성이 존재하지 않아도 된다.


public class Student {
private String name;
private Vector<Course> courses;

public Student(String name) {
this.name = name;
courses = new Vector<Course>();
}

public void registerCourse(Course course) {
courses.add(course);
}

public void dropCourse(Course course) {
if (courses.contains(course)) {
courses.remove(course);
}
}

public Vector<Course> getCourses() {
return courses;
}
}
public class Course {
private String name;

public Course(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

이 코드는 Student 객체 하나에 하나 이상의 Course 객체가 연관되어 있기 때문에 다중성을 구현했으며, Student 클래스에 대표적 컬렉션 자료구조인 Vector를 이용해 여러 개의 Course 클래스 객체를 참조할 수 있게 했다.

컬렉션 자료구조에는 Vector 외에도 Set, Map, ArrayList 등 여러 가지가 있으므로 상황에 맞는 적절한 자료구조를 선택해 사용하면 된다.

 

또한 이 코드는 Student 클래스에서 Course 클래스로 향하는 단방향 연관 관계이기 때문에 Course 클래스에는 Student 객체를 참조하는 속성이 정의되어 있지 않다.


Course 클래스 객체 하나에 Student 객체 하나만 연관되기를 원한다.

이는 한 과목에 한 명의 학생만 수강해야 한다는 이상한 관계가 된다.

이를 여러 명의 학생이 수강할 수 있는 일반적인 수강 관계로 개선하려면 다음과 같이 다중성을 수정할 필요가 있다.

다중성을 수정하면서 단방향 연관 관계가 양방향 연관 관계로 변했다. 일반적으로 다대다 연관 관계는 단방향 연관 관계가 아닌 양방향 연관 관계로 표현되는 것이 적절하다.

public class Student {
private String name;
private Vector<Course> courses;

public Student(String name) {
this.name = name;
courses = new Vector<Course>();
}

public void registerCourse(Course course) {
courses.add(course);
course.addStudent(this);
}

public void dropCourse(Course course) {
if (courses.contains(course)) {
courses.remove(course);
course.removeStudent(this);
}
}

public Vector<Course> getCourses() {
return courses;
}
}
public class Course {
private String name;
private Vector<Student> students;

public Course(String name) {
this.name = name;
students = new Vector<Student>();
}

public void addStudent(Student student) {
students.add(student);
}

public void removeStudent(Student student) {
students.remove(student);
}

public Vector<Student> getStudents() {
return students;
}

public String getName() {
return name;
}
}

Student 클래스와 Course 클래스의 연관 관계가 양방향 연관 관계이기 때문에 양쪽 클래스에서 서로를 참조할 수 있는 속성을 정의.

또한 다중성이 다대다이므로 참조 속성은 Vector를 이용함


양방향 연관 관계는 서로의 존재를 안다는 의미다. 그에 반해 단방향 연관 관계는 한 쪽은 알지만 다른 쪽은 상대방의 존재를 모른다는 의미이다.


*다음 설명에 맞는 클래스 다이어그램을 작성

- 학생은 반드시 한 학교에 소속되어야 한다.

- 학교는 학생이 반드시 100명 이상 있어야 한다.

 

다대다 연관 관계는 자연스럽게 양방향 관계가 되므로 구현하기가 생각보다 복잡하다. 따라서 보통 다대다 연관 관계를 일대다 단방향 연관 관계로 변환해 구현한다. 그런데 이를 살펴보기 전에 먼저 연관 클래스를 알아두는 것이 좋다.


성적 정보는 Student, Course 클래스 어디에도 둘 수 없다.

성적은 Student, Course 두 클래스 객체가 존재해야만 의미 있는 정보가 되기 때문이다.

 

ex. '홍길동 학생이 A+' -> 어떤 과목에서 A+를 받았다는 정보가 누락

    '소프트웨어 공학이 A+' -> 누가 A+를 받았다는 정보가 누락

'홍길동 학생이 소프트웨어 공학에서 A+를 받았다' 라고 할때만 의미가 통한다.

 

이처럼 성적 정보는 Student와 Course라는 두 클래스의 객체가 존재해야만 가치가 있다. 즉, 성적 정보는 클래스의 속성이 아닌 '수강하다'라는 연관 관계의 속성으로 따로 다뤄야 한다. 이런 경우 연관 클래스를 사용한다.


연관 클래스는 앞에서 설명했듯이 연관 관계에 추가할 속성이나 행위가 있을 때 사용한다.

다음 그림처럼 연관 관계가 있는 두 클래스 사이에 위치하며, 연관 관계를 나타내려고 연결하는 선 중앙에서 연관 클래스까지 점선을 사용해 연결한다.

연관 클래스에서 원하는 정보가 어떤 형태인지 몇 가지 예를 든다.

- 홍길동은 2013년에 개설한 소프트웨어 공학에서 A+를 받았다.

- 홍길서는 2013년에 개설한 소프트웨어 공학에서 C를 받았다.

- 홍길동은 2013년에 개설한 디자인 패턴에서 A를 받았다.

- 홍길서는 2013년에 개설한 디자인 패턴에서 B를 받았다.

- 홍길남은 2013년에 개설한 데이터베이스에서 B+를 받았다.

- 홍길동은 2012년에 개설한 디자인 패턴에서 D를 받았다.

위 예에서 Student 클래스와 Course 클래스, Transcript(성적) 클래스를 추출할 수 있다. Transcript 객체는 Student 객체와 COurse 객체를 연관시키는 객체이므로 Student 객체와 Course 객체를 참조할 수 있는 속성을 포함해야 한다. 또한 성적과 과목 개설년도와 같은 데이터는 Student 클래스나 Course 클래스에 속하지 않으며 두 클래스의 연관 정보이므로 이들도 Transcript 클래스의 속성이어야 한다.


그러나 학생 입장에서는 여러 과목의 성적을 받을 수 있고 마찬가지로 한 과목에서도 A+ ~ F라는 여러 가지 성적이 산출된다. 따라서 Student 클래스와 Transcript 클래스의 연관 관계의 다중성은 일대다이며 Course 클래스와 Transcript 클래스의 다중성도 일대다가 된다.


이를 기반으로 다시 표현한 것이다.

 

public class Student {
private Vector<Transcript> transcripts;
private String name;

public Student(String name) {
this.name = name;
transcripts = new Vector<Transcript>();
}

public void addTranscript(Transcript transcript) {
transcripts.add(transcript);
}

public Vector<Course> getCourses() {
Vector<Course> courses = new Vector<Course>();
Iterator<Transcript> itor = transcripts.iterator();

while (itor.hasNext()) {
Transcript tr = itor.next();
courses.add(tr.getCourse());
}
return courses;
}
}
@Getter
@Setter
public class Transcript {
private Student student;
private Course course;
private String date;
private String grade;

public Transcript(Student student, Course course) {
this.student = student;
this.student.addTranscript(this);
this.course = course;
this.course.addTranscript(this);
}

}
public class Course {
private Vector<Transcript> transcripts;
private String name;

public Course(String name) {
this.name = name;
transcripts = new Vector<Transcript>();
}

public void addTranscript(Transcript transcript) {
transcripts.add(transcript);
}

public String getName() {
return name;
}
}
public class Main {

public static void main(String[] args) {
Student s1 = new Student("manSup");
Student s2 = new Student("gilDong");
Course se = new Course("software Engineering");
Course designPattern = new Course("Design Pattern");

// manSup은 소프트웨어 공학 수강
Transcript t1 = new Transcript(s1, se);
// msnSup은 디자인 패턴 수강
Transcript t2 = new Transcript(s1, designPattern);
// gilDong은 디자인 패턴 수강
Transcript t3 = new Transcript(s2, designPattern);

// manSup은 2012년에 소프트웨어 공학에서 B0, 디자인 패턴에서 D+ 학점 취득
t1.setDate("1012");
t1.setGrade("B0");
t2.setDate("2012");
t2.setGrade("D+");

// gilDong은 2013년에 디자인 패턴에서 C+ 학점 취득
t3.setDate("2013");
t3.setGrade("C+");

//한 학생이 수강한 모든 과목을 구하는 작업을 실행
Vector<Course> courses;
courses = s1.getCourses();
for (int i = 0; i < courses.size(); i++) {
System.out.println(courses.get(i).getName());
}
}
}



원래 Student 클래스와 Course 클래스의 다중성이 다대다이므로 한 학생이 수강한 모든 과목을 구할 수 있어야 하고, 마찬가지로 한 과목을 수강하는 모든 학생을 구할 수 있어야 한다. 과목의 수강생을 구하는 메서드도 동일하게 구현 가능.

(위 소스에는 굳이 구현하지 않음)


그런데 같은 학생이 같은 과목을 여러 번 수강할 수도 있는 만큼 주어진 학생과 과목에 여러 개의 성적 정보가 연관될 수 있다.

따라서 이러한 이력이 모두 온전하게 표현되도록 수정할 필요가 있다.

 

이는 다음과 같은 클래스 다이어그램으로, 클래스 이름 아랫부분에 어떤 관계의 이력을 작성하면 된다.

학생의 도서관 대출 이력을 표현하고 싶다면 다음과 같은 클래스 다이어그램을 설계하면 된다.

연관 관계는 때로는 재귀적일 수 있다. 재귀적 연관 관계란 동일한 클래스에 속한 객체들 사이의 관계다.

예를 들어 직원이라는 클래스를 생각해보면 직원들 중에는 관리자 역할을 하는 직원도 있고 사원 역할을 하는 직원도 있다.

이를 간단하게 모델링하면 다음과 같다.

 

그런데 현실에서는 관리자 한 명이 여러 명의 사원들을 관리한다.

물론 때로는 관리해야 하는 사원이 전혀 없는 관리자가 있을 수도 있다. 역으로 어떤 사원은 관리자가 없을 수도 있다.(사장)

그래서 문제가 발생한다.

 

만약 '홍길동' 관리자가 '홍길서' 사원을 관리한다고 하고, '홍길서'도 '홍길남'을 관리하는 관리자라면 '홍길서' 사원이라는 객체는 '관리자'와 '사원' 이라는 두클래스에 동시에 속하는 모순이 발생한다.

 

즉, '관리자'와 '사원' 역할을 클래스로 만들면 시스템이 변화할 때 유연성이 부족할 수 있으므로 가급적 역할을 클래스로 만들지 않는 것이 좋다. 그래서 생긴 것이 재귀적 연관 관계다

그런데 위 그림과 같이 재귀적 연관 관계로 모델링해도 문제는 여전히 남아있다.

만약 '홍길동'이 '홍길서'를 관리하고 '홍길서'가 '홍길남'을 관리하는 상황에서 '홍길남'이 '홍길동'을 관리하는 상황도 있을 수 있기 때문이다. 이를 '관계의 루프'라고 하는데, 이런 상황을 배제하려면 다음과 같이 연관 관계에 제약을 설정해야 한다.

제약은 '{}' 안에 미리 정해진 제약뿐만 아니라 어떤 문장도 자유롭게 쓸 수 있으며 클래스나 연관 관계를 포함한 UML 모델 요소가 따라야 하는 규칙을 붙여줄 때 사용한다.

 

'{계층}'이란 객체 사이에 상하 관계가 존재하고 사이클이 존재하지 않는다는 의미이다.

 

'Design Pattern > References' 카테고리의 다른 글

인터페이스, 실체화 관계  (0) 2017.10.28
의존관계  (0) 2017.10.28
집합관계  (0) 2017.10.28
일반화관계  (0) 2017.10.28
클래스, 관계, UML  (0) 2017.10.28
Comments