프로그래밍 공부
작성일
2023. 5. 16. 14:39
작성자
WDmil
728x90

프로그래밍에서 객체관계는, 각 구조체 간의 상호작용을 정의한다.

 

이는 클래스간의 관계 라고 도 이해할 수 있으며, 총 5가지 유형으로 분류할 수 있다.

 

  1. 연관(Association)
    • Assocation은 두 클래스가 서로를 사용하는, 서로가 상속된 관계라고 할 수 있다. 이는 "사용한다" 또는 "알고있다"로 정의할 수 있는데, 서로가 서로의 값을 이용하거나 함수를 사용할 수 있는 관계라고 할 수 있다.
    • 예를들어 '학생'클래스와 '교수'클래스가 있다면, '학생'은 '교수'가 누구인지, 어떠한 과목을 가르치는지 알고, '교수'는 학생이 누구인지 몃학년인지 등의 정보를 알 수 있을것이다.
    • 이것은 두 클래스가 독립적으로 존재하면서도 서로를 참조할 수 있는 관계를 나타낸다.
  2. 집합(Aggregation)
    • Aggregation은 "전체 - 부분" 관계를 표현하며, "소유한다", "포함한다" 라고 이해할 수 있다.
    • 예를 들어 '대학'과 '학부' 가 있다면, 대학은 여러 개의 학부를 소유,포함 하고 학부는 특정 '대학' 에 속해있다. 그러나, 학부는 학부 자체로 나타낼 수 있으며, 이것은 '대학' 이 없어도 어떠한 '학부' 라고 표현할 수 있을것이다. 즉, 독립적으로 존재할 수 있다.
    • 이것은 전체와 부분 사이의 관계를 나타내며, 부분은 전체 없이도 독립적으로 존재할 수 있다.
  3. 합성(Composition)
    • Composition은 Aggregation과 유사하게 "전체 - 부분" 관계를 표현하지만, 이 경우 부분은 전체 없이는 존재할 수 없다.
    • 예를 들어 '사람' 과 '심장' 이 있다면, '사람' 은 '심장' 없이 존재할 수 없고, 마찬가지로 '심장' 은 '사람'없이 홀로 존재할 수 없을것이다.
    • 이것은 전체와 부분 사이의 강력한 결합을 나타낸다.
  4. 상속(Inheritance)
    • Inheritatnce는 "일반 - 특수" 관계를 표현하며, '확장하다' '받는다' 라고 이해할 수 있다.
    • 예를 들어 '동물' 과 '강아지' 가 있다면, '강아지' 는 '동물'의 어떠한 경우이고. '동물' 안에 포함된다고 할 수 있으나, '강아지' 는 동물의 특성을 받아온다 즉,'상속' 해 온다 라고 할수 있다.
    • 이것은 일반적인 개념에서 보다 구체적인 개념으로 확장하는 관계를 나타낸다.
  5. 의존(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에 의존한다. 라고 표현할 수 있다.

728x90