객체들의 관계
1. 프로그램이 수행해야 하는 기능 정하기
2. 어떤 객체들이 어떻게 나눠서 도움을 주고받을지 설계
3. 설계에 따라서 여러 클래스를 구현
구성(요소) 관계
Composition
- Part-of
- 두뇌는 육체의 일부이다. 육체 없이는 존재할 수 없으며, 두뇌가 육체 전체에 대해 알고 있지는 않다.
- 전체/부품 - 육체/두뇌
- 다른 클래스에도 속할 수 없음 - 두뇌는 다른 클래스에 속할 수 없음
- 멤버의 존재를 클래스가 관리함 - 두뇌를 육체가 관리함
- 단방향
Position2D.h
#pragma once
#include <iostream>
class Position2D
{
private:
int m_x;
int m_y;
public:
Position2D(const int & x_in, const int & y_in)
: m_x(x_in), m_y(y_in)
{}
//TODO: overload =
void set(const Position2D & pos_target)
{
set(pos_target.m_x, pos_target.m_y);
// m_x = pos_target.m_x;
// m_y = pos_target.m_y;
// => 아래와 기능이 겹치기 때문에 함수를 호출하는 것이 좋음
}
void set(const int & x_target, const int & y_target)
{
m_x = x_target;
m_y = y_target;
}
friend std::ostream & operator << (std::ostream & out, const Position2D & pos2d)
{
out << pos2d.m_x << " " << pos2d.m_y;
return out;
}
};
Monster.h
Position2D 자체는 재사용을 하려고 만든 클래스이지만, Monster에 속해 있는 Position2D 자체는 다른 클래스에서 사용할 필요가 없다. => 다른 클래스에 속할 수 없음!
Monster 클래스는 멤버로 선언된 Position2D의 기능을 사용하지만, 막상 Position2D 클래스는 Monster와는 상관없이 단순히 '들어온 값'에 의하여 기능을 수행하게 된다. => 단방향!
만약 Position2D 클래스에서도 Monster와 관련된 작업을 해야 한다면, 클래스의 설계가 잘못된 것.
#pragma once
//#include <iostream>
#include <string>
#include "Position2D.h"
class Monster
{
private:
std::string m_name; // string: char * data, unsigned length;
//int m_x; // location
//int m_y;
Position2D m_location; // sub class => 빨리 만드는 게 좋다
// 상위 클래스는 호출만 하고, 어떻게 작동하는지는 신경 쓰지 않는 편이 좋음 => 쪼개자!
public:
//Monster(const std::string name_in, const int & x_in, const int & y_in)
Monster(const std::string name_in, const Position2D & pos_in)
: m_name(name_in), m_location(pos_in)
{}
//void moveTo(const int & x_target, const int & y_target)
void moveTo(const Position2D & pos_target)
{
//m_x = x_target;
//m_y = y_target;
m_location.set(pos_target);
}
friend std::ostream & operator << (std::ostream & out, const Monster & monster)
{
//out << monster.m_name << " " << monster.m_x << " " << monster.m_y << std::endl;
out << monster.m_name << " " << monster.m_location;
return out;
}
};
main_chapter102.cpp
#include "Monster.h"
using namespace std;
int main()
{
Monster mon1("Sanson", Position2D(0, 0));
//mon1.m_location;
cout << mon1 << endl;
Monster mon2("Parsival", Position2D(0, 0));
//mon2.m_location;
// m_location은 mon1의 이름("Sanson")에 대해 알 필요가 없음
// m_location은 m_location의 기능만 하면 됨
//while (1) // gama loop
{
// event
//mon1.moveTo(1, 1);
mon1.moveTo(Position2D(1,1));
cout << mon1 << endl;
}
return 0;
}
집합 관계
Aggregation
- Has-a
- 사람이 자동차를 가지고 있다.
- 전체/부품 - 사람/자동차
- 다른 클래에스도 속할 수 있음 - 자동차는 공유할 수 있고, 팔 수도 있음
- 멤버의 존재를 클래스가 관리하지 않음 - 사람이 없어도 자동차 클래스는 존재 가능
- 단방향
Teacher.h
#pragma once
#include <string>
class Teacher
{
private:
std::string m_name;
//TODO: more members like home address, salary, age, evaluation, etc.
public:
Teacher(const std::string & name_in = "No Name")
: m_name(name_in)
{}
void setName(const std::string & name_in)
{
m_name = name_in;
}
std::string getName()
{
return m_name;
}
friend std::ostream & operator << (std::ostream & out, const Teacher & teacher)
{
out << teacher.m_name;
return out;
}
};
Student.h
#pragma once
#include <iostream>
#include <string>
class Student
{
private:
std::string m_name;
int m_intel; // intelligence;
//TODO: add more members like address, phone, favorite food, habits, ...
public:
Student(const std::string & name_in = "No Name", const int & intel_in = 0)
: m_name(name_in), m_intel(intel_in)
{}
void setName(const std::string & name_in)
{
m_name = name_in;
}
void setIntel(const int & intel_in)
{
m_intel = intel_in;
}
int getIntel()
{
return m_intel;
}
friend std::ostream & operator << (std::ostream & out, const Student & student)
{
out << student.m_name << " " << student.m_intel;
return out;
}
};
Lecture.h
#pragma once
#include <vector>
#include "Student.h"
#include "Teacher.h"
class Lecture
{
private:
std::string m_name;
Teacher teacher;
std::vector<Student> students;
//Teacher *teacher;
//std::vector<Student *> students;
public:
Lecture(const std::string & name_in)
: m_name(name_in)
{}
~Lecture()
{
// do NOT delete teacher
// do NOT delete students
}
void assignTeacher(const Teacher & const teacher_input)
{
teacher = teacher_input;
}
/*
void assignTeacher(Teacher * const teacher_input)
{
teacher = teacher_input;
}
*/
void registerStudent(const Student & const student_input)
{
students.push_back(student_input);
}
/*
void registerStudent(Student * const student_input)
{
students.push_back(student_input);
}
*/
void study()
{
std::cout << m_name << " Study " << std::endl << std::endl;
for (auto & element : students) //Note: 'auto element' doesn't work
element.setIntel(element.getIntel() + 1);
/*for (auto element : students)
(*element).setIntel((*element).getIntel() + 1);*/
}
friend std::ostream & operator << (std::ostream & out, const Lecture & lecture)
{
out << "Lecture name : " << lecture.m_name << std::endl;
out << lecture.teacher << std::endl;
for (auto element : lecture.students)
out << element << std::endl;
/*out << *lectrue.teacher << std::endl;
for (auto element : lecture.students)
out << *element << std::endl;*/
return out;
}
};
main_chapter103.cpp
#include <iostream>
#include <vector>
#include <string>
#include "Lecture.h"
int main()
{
using namespace std;
// Composition Relationship
Lecture lec1("Introduction to Computer Programming");
lec1.assignTeacher(Teacher("Prof. Hong"));
lec1.registerStudent(Student("Jack Jack", 0));
lec1.registerStudent(Student("Dash", 1));
lec1.registerStudent(Student("Violet", 2));
Lecture lec2("Computational Thinking");
lec2.assignTeacher(Teacher("Prof. Good"));
lec2.registerStudent(Student("Jack Jack", 0));
//TODO: implement Aggregation Relationship
// test
{
cout << lec1 << endl;
cout << lec2 << endl;
// event
lec2.study();
cout << lec1 << endl;
cout << lec2 << endl;
}
return 0;
}
Teacher.h
#pragma once
#include <string>
class Teacher
{
private:
std::string m_name;
//TODO: more members like home address, salary, age, evaluation, etc.
public:
Teacher(const std::string & name_in = "No Name")
: m_name(name_in)
{}
void setName(const std::string & name_in)
{
m_name = name_in;
}
std::string getName()
{
return m_name;
}
friend std::ostream & operator << (std::ostream & out, const Teacher & teacher)
{
out << teacher.m_name;
return out;
}
};
Student.h
#pragma once
#include <iostream>
#include <string>
class Student
{
private:
std::string m_name;
int m_intel; // intelligence;
//TODO: add more members like address, phone, favorite food, habits, ...
public:
Student(const std::string & name_in = "No Name", const int & intel_in = 0)
: m_name(name_in), m_intel(intel_in)
{}
void setName(const std::string & name_in)
{
m_name = name_in;
}
void setIntel(const int & intel_in)
{
m_intel = intel_in;
}
int getIntel()
{
return m_intel;
}
friend std::ostream & operator << (std::ostream & out, const Student & student)
{
out << student.m_name << " " << student.m_intel;
return out;
}
};
Lecture.h
#pragma once
#include <vector>
#include "Student.h"
#include "Teacher.h"
class Lecture
{
private:
std::string m_name;
// value의 vector이기 때문에 push_back을 하면 복사함
// &student_input != &students[0]
//Teacher teacher;
//std::vector<Student> students;
// students 자체는 vector라서 Lecture가 사라지면 students도 사라짐
// 단, Student 포인터를 담고 있기 때문에 가리키고 있던 Student 객체들은 사라지지 않음
Teacher *teacher;
std::vector<Student *> students;
public:
Lecture(const std::string & name_in)
: m_name(name_in)
{}
~Lecture()
{
// do NOT delete teacher
// do NOT delete students
}
/*
void assignTeacher(const Teacher & const teacher_input)
{
teacher = teacher_input;
}
*/
void assignTeacher(Teacher * const teacher_input)
{
teacher = teacher_input;
}
/*
void registerStudent(const Student & const student_input)
{
students.push_back(student_input);
}
*/
void registerStudent(Student * const student_input)
{
students.push_back(student_input);
}
void study()
{
std::cout << m_name << " Study " << std::endl << std::endl;
//for (auto & element : students) //Note: 'auto element' doesn't work
// element.setIntel(element.getIntel() + 1);
//for (auto & element : students) //Note: 'auto element' works
for (auto element : students)
element->setIntel(element->getIntel() + 1);
}
friend std::ostream & operator << (std::ostream & out, const Lecture & lecture)
{
out << "Lecture name : " << lecture.m_name << std::endl;
/*out << lecture.teacher << std::endl;
for (auto element : lecture.students)
out << element << std::endl;*/
out << *lecture.teacher << std::endl;
for (auto element : lecture.students)
out << *element << std::endl;
return out;
}
};
main_chapter103.cpp
#include <iostream>
#include <vector>
#include <string>
#include "Lecture.h"
int main()
{
using namespace std;
// Composition Relationship
Lecture lec1("Introduction to Computer Programming");
//lec1.assignTeacher(Teacher("Prof. Hong"));
//lec1.registerStudent(Student("Jack Jack", 0));
//lec1.registerStudent(Student("Dash", 1));
//lec1.registerStudent(Student("Violet", 2));
Lecture lec2("Computational Thinking");
//lec2.assignTeacher(Teacher("Prof. Good"));
//lec2.registerStudent(Student("Jack Jack", 0));
// Aggregation Relationship
// Composition 코드의 문제점:
// Student(Jack Jack,0)끼리 서로 다른 객체(다른 주소를 가짐)
Student std1("Jack Jack", 0);
Student std2("Dash", 1);
Student std3("Violet", 2);
Teacher teacher1("Prof. Hong");
Teacher teacher2("Prof. Good");
lec1.assignTeacher(&teacher1);
lec1.registerStudent(&std1);
lec1.registerStudent(&std2);
lec1.registerStudent(&std3);
lec2.assignTeacher(&teacher2);
lec2.registerStudent(&std1);
// 1. 메인 함수에서 변수로 선언(상단 구현)
// 2. 다른 함수에서 사용할 때는 동적 할당으로 구현, 똑같이 사용 가능
Student *std11 = new Student("Jack Jack", 0);
Student *std22 = new Student("Dash", 1);
Student *std33 = new Student("Violet", 2);
Teacher *teacher11 = new Teacher("Prof. Hong");
Teacher *teacher22 = new Teacher("Prof. Good");
// test
{
cout << lec1 << endl;
cout << lec2 << endl;
// event
lec2.study();
cout << lec1 << endl;
cout << lec2 << endl;
}
//TODO: class HobbyClub
//TODO: delete memory (if necessary)
delete std11;
delete std22;
delete std33;
delete teacher11;
delete teacher22;
return 0;
}
분산 처리할 때는 메모리가 분리되어 있기 때문에 불가능! 한 쪽에서 업데이트하면, 다른 쪽에서도 업데이트해야 하기 때문에 동기화 과정을 거쳐야 함!
제휴(연계) 관계
Association
- Uses-a
- 환자는 의사의 치료를 받는다. 의사는 환자들로부터 치료비를 받는다.
- 서로가 서로를 사용하는 관계
- 용도 외엔 관계 없음
- 다른 클래스에도 속할 수 있음
- 멤버의 존재를 클래스가 관리하지 않음, 서로가 서로를 관리할 수 없음
- 단방향 or 양방향(을 더 많이 사용)
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Doctor; // 전방 선언 forward declaration
// 파일 분리 시, 전방 선언한 class가 어디 있는지 찾을 수 없는 경우가 발생할 수 있음!!
class Patient
{
private:
string m_name;
vector<Doctor*> m_doctors; // error! doctor undeclared identifier
public:
Patient(string name_in)
: m_name(name_in)
{}
void addDoctor(Doctor * new_doctor)
{
m_doctors.push_back(new_doctor);
}
void meetDoctors();
/*
// 전방 선언 시 error! 전방 선언 안에 m_name이 있는지, 없는지를 알 수 없음
// 따라서 body를 Doctor 클래스의 하단으로 빼 줌
void meetDoctors()
{
for (auto & ele : m_doctors)
{
cout << m_name << " - Meet doctor : " << ele->m_name << endl;
}
}*/
friend class Doctor;
};
class Doctor
{
private:
string m_name;
// 포인터를 쓰지 않고, 환자 고유번호(id)를 부여하여 해당 환자를 찾는 방식으로도 구현 가능
vector<Patient*> m_patients;
Doctor * doctor; // reflexive association
public:
Doctor(string name_in)
: m_name(name_in)
{}
void addPatient(Patient * new_patient)
{
m_patients.push_back(new_patient);
}
void meetPatient()
{
for (auto & ele : m_patients)
{
cout << m_name << " - Meet patient : " << ele->m_name << endl;
}
}
// friend class 선언으로 직접 접근할 수 있게 함
friend class Patient;
};
void Patient::meetDoctors()
{
for (auto & ele : m_doctors)
{
// 전방 선언 시 error! 전방 선언 안에 m_name이 있는지, 없는지를 알 수 없음
cout << m_name << " - Meet doctor : " << ele->m_name << endl;
}
}
int main()
{
// 어느 한쪽이 확실히 주가 되지 않는 경우(주-주, 부-부)
Patient *p1 = new Patient("Jack Jack");
Patient *p2 = new Patient("Dash");
Patient *p3 = new Patient("Violet");
Doctor *d1 = new Doctor("Doctor K");
Doctor *d2 = new Doctor("Doctor L");
p1->addDoctor(d1);
d1->addPatient(p1);
p2->addDoctor(d2);
d2->addPatient(p2);
p2->addDoctor(d1);
d1->addPatient(p2);
// patients meet doctors
p1->meetDoctors();
// doctors meet patients
d1->meetPatient();
// deletes
delete p1;
delete p2;
delete p3;
delete d1;
delete d2;
return 0;
}
의존 관계
Dependency
- Depends-on
- 나는 목발을 짚었다.
- 용도 외엔 관계 없음, 헤더파일에서 사라져도 무관, 대부분의 경우 일시적으로 사용
- 다른 클래스에도 속할 수 있음
- 멤버의 존재를 클래스가 관리함, 엄연히 따지자면 멤버로 존재하는 건 아니고 그 '존재'를 클래스가 관리한다는 뜻, 멤버로 들어가지도 않는 경우가 많다
- 단방향
- 가장 많이 사용할 것임
Timer.h
#pragma once
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>
class Timer
{
using clock_t = std::chrono::high_resolution_clock;
using second_t = std::chrono::duration<double, std::ratio<1>>;
std::chrono::time_point<clock_t> start_time = clock_t::now();
public:
void elapsed()
{
std::chrono::time_point<clock_t> end_time = clock_t::now();
std::cout << std::chrono::duration_cast<second_t>(end_time - start_time).count() << std::endl;
}
};
Worker.h
#pragma once
class Worker
{
public:
void doSomething();
};
Worker.cpp
#pragma once
#include "Worker.h"
#include "Timer.h"
// Worker를 선언할 때는 Timer.h가 필요 없고,
// cpp 파일에서 구현할 때면 include 해 주면 됨
void Worker::doSomething()
{
Timer timer; // start timer
// do some work here
timer.elapsed(); // end timer and report elapsed time
}
main.cpp
#include "Worker.h"
int main()
{
Worker().doSomething();
return 0;
}
컨테이너 클래스
다른 클래스들을 담는 역할을 하는 클래스
#include <iostream>
#include <vector>
using namespace std;
class IntArray
{
private:
int m_length = 0;
int *m_data = nullptr;
public:
//Constructors
//Destructors
//Initialize()
//reset(); // 메모리 싹 지우기
//resize();
//insertBefore(const int & value, const int & ix);
//remove(const int & ix);
//push_back(const int& value);
//output operator overloading
};
int main()
{
IntArray my_arr{ 1, 3, 5, 7, 9 };
my_arr.insertBefore(10, 1); // 1, 10, 3, 5, 7, 9
my_arr.remove(3); // 1, 10, 3, 7, 9
my_arr.push_back(13); // 1, 10, 3, 7, 9, 13
//standard template library: member of 관계
//vector<int> int_vec;
//array<int, 10> int_arr;
return 0;
}
매개변수를 어떻게 받아야 할지도 모르겠고 너무 더럽게 짠 것 같다... 시간 남으면 수정해 봐야겠다
#include <iostream>
#include <vector>
using namespace std;
class IntArray
{
private:
int m_length = 0;
int *m_data = nullptr;
public:
//TODO: 매개변수를 어떻게 받아야 할지 잘 모르겠다..
IntArray(int n1, int n2, int n3, int n4, int n5)
{
m_length = 5;
m_data = new int[m_length];
m_data[0] = n1;
m_data[1] = n2;
m_data[2] = n3;
m_data[3] = n4;
m_data[4] = n5;
}
~IntArray()
{
delete[] m_data;
}
void initialize(int idx, int value)
{
if (m_data != nullptr) return;
m_data[idx] = value;
}
void reset()
{
if(m_data != nullptr)
delete[] m_data;
}
void resize()
{
int *temp = new int[m_length + 1];
memcpy(temp, m_data, m_length * sizeof(int));
reset();
m_data = temp;
++m_length;
}
// 10, 1
// 1, 10, 3, 5, 7, 9
void insertBefore(const int & value, const int & ix)
{
resize();
int *temp = new int[m_length - ix - 1];
for (int i = 0; i < m_length; i++)
{
temp[i] = m_data[i + ix];
}
memcpy(&m_data[ix+1], temp, (m_length - ix - 1) * sizeof(int));
m_data[ix] = value;
}
// 3
// 1, 10, 3, 7, 9
void remove(const int & ix)
{
int *temp = new int[m_length - 1];
for (int i = ix + 1; i < m_length; i++)
{
temp[i - ix - 1] = m_data[i];
}
memcpy(&m_data[ix], temp, (m_length - ix - 1) * sizeof(int));
--m_length;
}
void push_back(const int& value)
{
resize();
m_data[m_length-1] = value;
}
void printTest()
{
for (int i = 0; i < m_length; i++)
cout << m_data[i] << " ";
cout << endl;
}
friend ostream& operator << (ostream & out, const IntArray & arr)
{
for (int i = 0; i < arr.m_length; i++)
out << arr.m_data[i] << " ";
out << endl;
return out;
}
};
int main()
{
IntArray my_arr{ 1, 3, 5, 7, 9 };
cout << my_arr;
my_arr.insertBefore(10, 1); // 1, 10, 3, 5, 7, 9
cout << my_arr;
my_arr.remove(3); // 1, 10, 3, 7, 9
cout << my_arr;
my_arr.push_back(13); // 1, 10, 3, 7, 9, 13
cout << my_arr;
my_arr.push_back(15);
cout << my_arr;
my_arr.push_back(17);
cout << my_arr;
my_arr.remove(1);
cout << my_arr;
my_arr.remove(0);
cout << my_arr;
//standard template library: member of 관계
//vector<int> int_vec;
//array<int, 10> int_arr;
return 0;
}
해당 포스트는 '홍정모의 따라하며 배우는 C++' 강의를 수강하며 개인 백업용으로 메모하였습니다.