플로렌스라는 개발자

C++ is-a 관계와 has-a 관계

프로그래밍/C++ 2019.10.18 댓글 0 Plorence

목차

    is-a 관계

    is-a 관계는 파생 클래스에 있어서 상속의 조건인데 is-a 관계의 의미인 ~은 ~이다라는 관계성립하자는 것입니다.

    public 상속을 사용할 때는 is-a관계가 성립되도록 만들어야 합니다.

    예를 들어서 "사과는 과일이다"은 성립합니다. 사과는 과일이니까요.

    틀린 예로 "과일은 사과이다."가 있겠습니다. 문장 그대로 과일이라는 단어는 사과를 의미하지 않으니까요.

     

    그래서 Apple 클래스는 파생클래스로 Fruit 클래스를 상속할 수 있는 것입니다.

     

    is-a관계여야만 하는 이유

    여러가지 과일의 대한 클래스를 만들 때, 기본적으로 그 사과들이 가지고 있는 공통적인 특징추상화한 클래스가 기초 클래스고 상속받은 여러가지 과일 클래스가 파생 클래스입니다.

     

    이처럼 범위가 좁아지면서 기능이 기초 클래스보단 많아집니다.

    기능이 많아지면 C++의 상속적 특성과 일치합니다.

    그래서 is-a관계를 성립시키는 것이 상속을 하는 조건에서 반드시 필요로 합니다.

     

    has-a 관계

    has-a관계의 의미는 ~가 ~을 소유한다라는 의미인데,결론만 말하면 

    클래스의 데이터 멤버객체포함시키는게 좋습니다.

    has-a관계가 성립되면 상속도 가능하지만 1순위로 is-a관계를 지키는 것이 좋습니다.

    (애초에 소유하다는 포함하다라고 해석할 수 있으니 상속보다는 클래스의 데이터 멤버로 객체를 포함시키는 게 좋습니다.) 

     

    예를 들어서 "나(사람)는 사과를 갖고 있다."는 is-a관계에 성립되지 않고 has-a관계에 성립됩니다. 갖고 있다라는것은 뭐가 뭐를 포함하다는 의미도 되니 상속보다는 데이터 멤버가 더 어울립니다.

     

    has-a 관계(~가~를 가지고 있다.)에서 모델링 하는 방법은 두 가지가 있는데 그중에 한 가지는 일반적인 방법으로, 컴포지션(컨테인먼트)을 사용하는 것입니다.

    (나머지 한 가지는 private 상속을 사용하는 방법입니다.)

    즉 다른 클래스의 객체들을 멤버로 가지는 클래스를 만드는 겁니다.

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <valarray>
    using namespace std;
    class Person {
    private:
            int x=0;
            int y=0;
            char * name = 0;
    public:
            Person(const char * p_name) {
                   name = new char[strlen(p_name) + 1];
                   strcpy(name, p_name);
            }
            Person(const Person & per) { //복사 생성자
                   name = new char[strlen(per.name) + 1];
                   strcpy(name, per.name);
                   x = per.x;
                   y = per.y;
            }
            Person & operator=(const Person & per) { //기초 클래스 대입 연산자 오버로딩
                   if (this == &per) {
                           return *this;
                   }
                   delete[] name;
                   name = new char[strlen(per.name) + 1];
                   strcpy(name, per.name);
                   x = per.x;
                   y = per.y;
                   return *this;
            }
            virtual void Show() {
                   cout << "이름:" << name << endl;
            }
            virtual ~Person() { //파괴자
                   delete[] name;
            }
    };
    class Student : public Person {
    private:
            char * aff_school = 0;
            valarray<double> scores; //내포된객체
            //국어,영어,수학점수만
    public:
            Student(const char * p_name, const char * p_aff_school) : Person(p_name){
                   aff_school = new char(strlen(p_aff_school) + 1);
                   strcpy(aff_school, p_aff_school);
                   scores = valarray<double>(3); //3개짜리 배열 생성
            }
            Student(const Student & stu) : Person(stu) { //복사 생성자
                   aff_school = new char[strlen(stu.aff_school) + 1];
                   strcpy(aff_school, stu.aff_school);
            }
            
            Student & operator=(const Student & stu) { //파생 클래스 대입 연산자 오버로딩
                   if (this == &stu) {
                           return *this;
                   }
                   delete[] aff_school;
                   Person::operator=(stu); //명시적 대입 연산자 호출로 기초 클래스 부분을  복사함
                   aff_school = new char[strlen(stu.aff_school) + 1];
                   strcpy(aff_school, stu.aff_school);
                   return *this;
            }
            void Input(double sub1, double sub2, double sub3) {
                   scores[0] = sub1; //국어
                   scores[1] = sub2; //영어
                   scores[2] = sub3; //수학
            }
            void Show() {
                   cout << "소속 학교 명:" << aff_school << endl;
                   Person::Show();
                   cout << "국어 점수:" << scores[0] << endl;
                   cout << "영어 점수:" << scores[1] << endl;
                   cout << "수학 점수:" << scores[2] << endl;
            }
            
            ~Student() { //파괴자
                   delete[] aff_school;
            }
    };
    int main(void) {
            Person per = Person("철수");
            Student stu = Student("민지", "마포고");
            stu.Input(50, 60, 70);
            stu.Show();
    }

    (Student 클래스, 그 중에서도 42번째 줄만 보셔도 됩니다.)

    valarray라는 클래스를 내포된 객체(scores)로 선언을 했는데 초기화 방식은 멤버 초기자 리스트나 생성자 내에서 해도 됩니다.

    아니면 C++11에서 부터 가능한 In-Class 방식으로 해도 됩니다.

    만약 멤버 초기자 리스트를 사용하지 않았을 경우에는 C++은 어떤 객체의 나머지 부분이 생성되기 전에 모든 멤버 객체들이 먼저 생성되어야 한다고 요구하기 때문에 멤버 객체 클래스를 위해 정의된 디폴트 생성자를 사용합니다.

    이 scores라는 객체 멤버는 학생의 국어,영어,수학 점수를 저장하기 위해 존재합니다.(성적표)

    성적표와 학생은 is-a관계보단 has-a관계에 가까우므로 모델링하는 방법 중 하나인 컴포지션을 사용하는게 맞습니다.

     

    내포된 객체의 인터페이스 사용

    scores.size() //원소 개수를 리턴

    일반적인 클래스 멤버 함수 사용이랑 별 다를 게 없습니다.

    댓글

    0