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

프로그래밍/C++ 2019.10.18 댓글 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() //원소 개수를 리턴

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

댓글