프로그래밍에서 객체관계는, 각 구조체 간의 상호작용을 정의한다.
이는 클래스간의 관계 라고 도 이해할 수 있으며, 총 5가지 유형으로 분류할 수 있다.
- 연관(Association)
- Assocation은 두 클래스가 서로를 사용하는, 서로가 상속된 관계라고 할 수 있다. 이는 "사용한다" 또는 "알고있다"로 정의할 수 있는데, 서로가 서로의 값을 이용하거나 함수를 사용할 수 있는 관계라고 할 수 있다.
- 예를들어 '학생'클래스와 '교수'클래스가 있다면, '학생'은 '교수'가 누구인지, 어떠한 과목을 가르치는지 알고, '교수'는 학생이 누구인지 몃학년인지 등의 정보를 알 수 있을것이다.
- 이것은 두 클래스가 독립적으로 존재하면서도 서로를 참조할 수 있는 관계를 나타낸다.
- 집합(Aggregation)
- Aggregation은 "전체 - 부분" 관계를 표현하며, "소유한다", "포함한다" 라고 이해할 수 있다.
- 예를 들어 '대학'과 '학부' 가 있다면, 대학은 여러 개의 학부를 소유,포함 하고 학부는 특정 '대학' 에 속해있다. 그러나, 학부는 학부 자체로 나타낼 수 있으며, 이것은 '대학' 이 없어도 어떠한 '학부' 라고 표현할 수 있을것이다. 즉, 독립적으로 존재할 수 있다.
- 이것은 전체와 부분 사이의 관계를 나타내며, 부분은 전체 없이도 독립적으로 존재할 수 있다.
- 합성(Composition)
- Composition은 Aggregation과 유사하게 "전체 - 부분" 관계를 표현하지만, 이 경우 부분은 전체 없이는 존재할 수 없다.
- 예를 들어 '사람' 과 '심장' 이 있다면, '사람' 은 '심장' 없이 존재할 수 없고, 마찬가지로 '심장' 은 '사람'없이 홀로 존재할 수 없을것이다.
- 이것은 전체와 부분 사이의 강력한 결합을 나타낸다.
- 상속(Inheritance)
- Inheritatnce는 "일반 - 특수" 관계를 표현하며, '확장하다' '받는다' 라고 이해할 수 있다.
- 예를 들어 '동물' 과 '강아지' 가 있다면, '강아지' 는 '동물'의 어떠한 경우이고. '동물' 안에 포함된다고 할 수 있으나, '강아지' 는 동물의 특성을 받아온다 즉,'상속' 해 온다 라고 할수 있다.
- 이것은 일반적인 개념에서 보다 구체적인 개념으로 확장하는 관계를 나타낸다.
- 의존(Dependency)
- Dependency 관계는 한 구조체, 클래스가 다른 클래스의 메서드 내에서 사용될때를 말하며, "사용한다"라고 표현한다.
- 예를 들어 '학생' 과 '교과서' 가 있다고 한다면, '학생' 이 '수업' 이라는 행동을 할 때 '교과서' 라는 것에 의존하게 되는것을 말한다. '학생' 이 '수업'에 '교과서' 가 없으면 '수업'을 수행할 수 없기 때문이다.
- 이것은 여러개의 관계에서 동작을 수행하기위한 최소조건을 다른 클래스에 '의존'하는 관계를 나타낸다.
다음은 연관 Association의 코드 예시이다.
#include <iostream>
#include <vector>
using namespace std;
// 특정 클래스가 다른 클래스의 맴버 를 선언한 경우.
// 서로 연계가 되는 관계. 의사와 환자 의 경우.
// 커플링 관계
class Patient
{
string name;
friend class Doctor;
vector<class Doctor*> doctors;
public:
Patient(const string& name)
:name(name)
{}
void AddDoctor(class Doctor* const doctor)
{
doctors.push_back(doctor);
}
void MeetDoctor();
};
class Doctor
{
string name;
friend class Patient;
vector<class Patient*> patients;
public:
Doctor(const string& name)
:name(name)
{}
void AddPatient(class Patient* const patient)
{
patients.push_back(patient);
}
void MeetPatient()
{
for (const auto& patient : patients)
cout << "Meet Patient : " << patient->name << endl;
}
};
void Patient::MeetDoctor()
{
for (const auto& doctor : doctors)
cout << "Meet Patient : " << doctor->name << endl;
}
int main()
{
Patient* p1 = new Patient("Kim");
Patient* p2 = new Patient("Lee");
Patient* p3 = new Patient("Park");
Doctor* d1 = new Doctor("Doc. Lee");
Doctor* d2 = new Doctor("Doc. Kim");
p1->AddDoctor(d1);
d1->AddPatient(p1);
p2->AddDoctor(d2);
d2->AddPatient(p2);
p3->AddDoctor(d2);
d2->AddPatient(p3);
// 서로가 서로에게 영향을 끼치기 때문에, 하나가 수정되면 오류가 폭팔할 수 있다. 그래서 별로 좋은 코드가 아니다.
delete d2;
delete d1;
delete p3;
delete p2;
delete p1;
// 선언된 클래스들은 후입선출 방식으로 해제해주어야 한다. 처음 선언된 클래스는 얼마나 엮여있을지 알 수 없기 때문.
return 0;
}
위 코드에서는, Doctor이라는 클래스와 Patient라는 클래스가 서로의 값을 사용하게 된다.
Doctor(의사)는 AddPatient로 입력되었던 Patient(환자)의 값이 들어있는 vector을,
Patient(환자)는 AddDoctor로 입력되었던 Doctor(의사)의 값이 들어있는 vector을
서로 사용하여 서로가 서로를 의존한다. 즉,연관되어있다.
각 클래스는 환자 또는 의사 둘중 한개만 사라져도. 동작하지 않는 위험한 코드가 되어버렸다.
위와같은 방식의 코드사용은 회피하는것 이 좋다.
다음은 집합 Aggregation 의 코드 예시이다.
#pragma once
#include <iostream>
#include <vector>
class Student
{
std::string name;
int count;
public:
Student(const std::string name, int count = 0)
:name(name), count(count)
{}
auto GetName() const { return name; }
void SetName(const std::string& name) { this->name = name; }
auto Getcount() const { return count; }
void Setcount(const int& count) { this->count = count; }
};
class Teacher
{
std::string name;
public:
Teacher(const std::string& name)
:name(name)
{}
auto GetName() const { return name; }
void SetName(const std::string& name) { this->name = name; }
};
class Lecture
{
std::string name;
Teacher* teacher;
std::vector<Student*> students;
public:
Lecture(const std::string& name)
: name(name)
{}
void AssignmentTeacher(Teacher* teacher)
{
this->teacher = teacher;
}
void AssignmentStudent(Student* student)
{
students.push_back(student);
}
void Print()
{
std::cout << "teacher : " << teacher << std::endl;
for (const auto& student : students)
std::cout << "student : " << student << std::endl;
std::cout << std::endl;
}
};
int main()
{
using namespace std;
Teacher t("teacher");
Student s1("a");
Student s2("b");
Student s3("c");
cout << "t : " << &t << endl;
cout << "s1 : " << &s1 << endl;
cout << "s1 : " << &s2 << endl;
cout << "s1 : " << &s3 << endl;
cout << endl;
{
Lecture lec("C++");
lec.AssignmentTeacher(&t);
lec.AssignmentStudent(&s1);
lec.AssignmentStudent(&s2);
lec.AssignmentStudent(&s3);
lec.Print();
}
cout << "t : " << &t << endl;
cout << "s1 : " << &s1 << endl;
cout << "s1 : " << &s2 << endl;
cout << "s1 : " << &s3 << endl;
return 0;
// lecture는 집합관계이다.
}
위 코드에서 수업, lec을 구성하기 위해서는 수업이름을 생성할 때 생성자에 넣어주고, lec에 수업에 참여할 교사와 학생을 입력하여 진행하게 된다.
여기서, 각 Teacher과 Student는 독립적 이지만, 각각의 클래스가 Lecture에서 만나 "집합" 되어 동작하기 때문에, Lecture에 대한 집합관계 라고 해석할 수 있다.
집합관계 이기 때문에, 집합되는 주체인 Lecture가 사라지더라도, Teacher와 Student에는 영향이 가지 않는다.
다음은 합성 Composition 의 코드 예시이다.
#include <iostream>
using namespace std;
class Point2D
{
public:
int x;
int y;
};
class Monster
{
string name;
Point2D Position1;
Point2D* Position2;
public:
// composition
Monster(int x, int y)
{
Position1.x = x;
Position1.y = y;
// 이렇게 쓰면 강력한 연관관계가 되어버린다.
// 이는 Point2D가 없어지면 Monster가 동작하지 않기 때문,
}
// Aggregation
Monster(Point2D* point)
{
this->Position2 = point;
// 종료되더라도 포인터의 원본값은 사라지지 않는다.
}
};
int main()
{
Monster mon1(10, 20);
Point2D point;
Monster mon2(&point);
return 0;
}
위 코드에서 Monster이라는 class에 Point2D라는 class를 입력하여 데이터를 연산하였다.
여기서 Monster는 Point2D가 없다면 동작하지 않고, 코드에 오류가 발생할 것 이다.
이러한 강력한 연관관계를 Composition 이라고 한다.
다음은 상속 Composition 의 코드 예시이다.
#include <iostream>
// Engine 클래스 정의
class Engine {
public:
void Start() {
std::cout << "Engine started." << std::endl;
}
};
// Car 클래스 정의
class Car {
private:
Engine engine; // Engine 객체를 멤버 변수로 갖음
public:
void StartCar() {
engine.Start(); // Engine 객체의 Start() 메서드 호출
std::cout << "Car started." << std::endl;
}
};
int main() {
Car myCar;
myCar.StartCar(); // Car 객체의 StartCar() 메서드 호출
return 0;
}
위 코드에서는 Engine이라는 class가 부모 클래스가 되고, Car이 그 값을 상속받아 진행하게 된다.
Car가 실행되면, 부모class인 Engine이 먼저 호출되고, 그다음 Car의 데이터가 실행되는. 과정을 거치게된다.
다음은 의존 Dependencies 의 코드 예시이다.
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;
// 의존관계 특정한 클래스가 다른 클래스를 잠깐 사용하는 형태.
class Timer
{
time_point<high_resolution_clock> start_time;
public:
Timer()
{
start_time = high_resolution_clock::now();
}
void Elapsed()
{
auto current_time = high_resolution_clock::now();
duration<double, std::milli> delta = current_time - start_time;
cout << "Second : " << delta.count()/1000 << endl;
}
};
class Worker
{
public:
void DoSomething()
{
Timer timer;
for (int i = 0; i < 100'000'000; i++);
timer.Elapsed();
}
};
int main()
{
Worker w;
w.DoSomething();
return 0;
}
위 코드에서는, Timer이 작성되고, Worker에서 Timer을 사용하여 함수를 기입하게 된다.
이때, Worker는 Timer에 의존하지만, Timer는 Worker와 관계없이 동작하게 된다.
이를 Worker이 Timer에 의존한다. 라고 표현할 수 있다.