250x250
Notice
Recent Posts
Recent Comments
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

SYDev

C++ Chapter 07-4 : 상속을 위한 조건 본문

Programming Lang/C++

C++ Chapter 07-4 : 상속을 위한 조건

시데브 2023. 7. 18. 20:06
728x90

 상속으로 클래스의 관계를 구성하기 위해서는 조건이 필요하다. 조건과 그에 따른 필요조건이 충족되지 않는다면, 상속은 하지 않는 것만 못하다.

 

상속을 위한 기본 조건인 IS-A 관계의 성립

 IS-A 관계가 의미하는 것은 간단하다. 컴퓨터와 노트북 컴퓨터를 생각해보자. 노트북 컴퓨터는 컴퓨터의 특징을 모두 가지고 거기에 자신만의 "이동성"이라는 특징이 추가된다. 그렇기 때문에 노트북 컴퓨터는 일종의 컴퓨터라고 할 수 있다. 이를 바꿔서 말하면 "노트북 컴퓨터 is a 컴퓨터"라고 표현할 수 있는데, 여기서 사용되는 is a 라는 두 단어가 성립되는 관계가 IS-A관계이다.

 

 이를 클래스의 개념에서 다시 살펴보자. 클래스 Computer와 이를 상속하는 NotebookComp가 있다면 둘은 IS-A 관계가 성립한다고 볼 수 있다. 그리고 우리가 두 클래스를 상속관계로 묶고자 할 때 이런 IS-A 관계가 성립하지 않는다면 적절한 상속의 관계가 아닐 확률이 높다.

 

 이와 관련된 예제를 살펴보자.

#include <iostream>
#include <cstring>
using namespace std;

class Computer
{
private:
    char owner[50];
public:
    Computer(char * name)
    {
        strcpy(owner, name);
    }
    void Calculate()
    {
        cout<<"요청 내용을 계산합니다."<<endl;
    }
};

class NotebookComp : public Computer
{
private:
    int Battery;
public:
    NotebookComp(char * name, int initChang)
        : Computer(name), Battery(initChang)
    { }
    void Charging() {Battery+=5;}
    void UseBattery() {Battery-=1;}
    void MovingCal()
    {
        if(GetBatteryInfo()<1)
        {
            cout<<"충전이 필요합니다."<<endl;
            return;
        }
        cout<<"이동하면서 ";
        Calculate();
        UseBattery();
    }
    int GetBatteryInfo() {return Battery;}
};

class TabletNoteBook : public NotebookComp
{
private:
    char regstPenModel[50];
public:
    TabletNoteBook(char * name, int initChang, char * pen)
        : NotebookComp(name, initChang)
    {
        strcpy(regstPenModel, pen);
    }
    void Write(char * penInfo)
    {
        if(GetBatteryInfo()<1)
        {
            cout<<"충전이 필요합니다."<<endl;
            return;
        }
        if(strcmp(regstPenModel, penInfo)!=0)
        {
            cout<<"등록된 펜이 아닙니다.";
            return;
        }
        cout<<"필기 내용을 처리합니다."<<endl;
        UseBattery();
    }
};

int main(void)
{
    NotebookComp nc("이수종", 5);
    TabletNoteBook tn("정수영", 5, "ISE-241-242");
    nc.MovingCal();
    tn.Write("ISE-241-242");

    return 0;
}
이동하면서 요청 내용을 계산합니다.
필기 내용을 처리합니다.

 위 예제에서 클래스들의 상속 관계는 다음과 같이 표현할 수 있다.

  • NotebookComp는 Computer이다.
  • TabletNotebook은 NoteBookComp이다.
  • 따라서, TabletNotebook은 Computer이다.

 이 클래스의 관계를 'UML(Unified Modeling Language)' 표기법으로 표현하면 다음과 같다.

UML의 상속 표현

 위 그림에서 화살표는 상속을 의미하는데, 화살표의 머리는 기초 클래스로 향한다.

 

HAS-A 관계

 IS-A 관계 상속이 형성될만한 관계로 HAS-A 관계가 있다. HAS-A 관계 '소유'의 관계를 의미한다.

 

 이와 관련된 다음 예제를 살펴보자.

 

 파일명 : HASInheritance.cpp

#include <iostream>
#include <cstring>
using namespace std;

class Gun
{
private:
    int bullet;     // 장전된 총알의 수
public:
    Gun(int bnum) : bullet(bnum)
    { }
    void Shot()
    {
        cout<<"BBANG!"<<endl;
        bullet--;
    }
};

class Police : public Gun
{
private:
    int handcuffs;  //소유한 수갑의 수
public:
    Police(int bnum, int bcuff)
        : Gun(bnum), handcuffs(bcuff)
    { }
    void PutHandcuff()
    {
        cout<<"SNAP!"<<endl;
        handcuffs--;
    }
};

int main(void)
{
    Police pman(5, 3);  //총알 5, 수갑 3
    pman.Shot();
    pman.PutHandcuff();

    return 0;
}
BBANG!
SNAP!

 위 예제는 권총을 소유한 경찰을 표현하고 있다. 이를 다르게 표현하면 "경찰 has a 총"이듯이 두 클래스는 HAS-A 관계가 성립하고 있다. 그러나, 이러한 HAS-A 관계는 다른 방식으로도 얼마든지 표현이 가능하다.

 

 다음 예제는 위 예제의 관계를 상속이 아닌 다른 방식으로 표현하고 있다.

 

 파일명 : HASComposite.cpp

#include <iostream>
#include <cstring>
using namespace std;

class Gun
{
private:
    int bullet;     // 장전된 총알의 수
public:
    Gun(int bnum) : bullet(bnum)
    { }
    void Shot()
    {
        cout<<"BBANG!"<<endl;
        bullet--;
    }
};

class Police 
{
private:
    int handcuffs;  //소유한 수갑의 수
    Gun * pistol;   //소유하고 있는 권총
public:
    Police(int bnum, int bcuff)
        : handcuffs(bcuff)
    { 
        if(bnum>0)
            pistol = new Gun(bnum);
        else
            pistol = NULL;
    }
    void PutHandcuff()
    {
        cout<<"SNAP!"<<endl;
        handcuffs--;
    }
    void Shot()
    {
        if(pistol==NULL)
            cout<<"Hut BBANG!"<<endl;
        else
            pistol ->Shot();
    }
    ~Police()
    {
        if(pistol != NULL)
            delete pistol;
    }
};

int main(void)
{
    Police pman1(5, 3);
    pman1.Shot();
    pman1.PutHandcuff();

    Police pman2(0, 3);     //권총을 소유하지 않은 경찰
    pman2.Shot();
    pman2.PutHandcuff();

    return 0;
}
BBANG!
SNAP!
Hut BBANG!
SNAP!

 위 예제는 코드의 길이가 훨씬 늘어났음에도  HASInheritance.cpp보다 훨씬 좋은 모델이다. 그 이유로 이전 예제가 코드의 길이가 늘어날 가능성이 더 높은 모델이기 때문이라고 할 수 있다. HASInheritance.cpp에서 보여준 방식으로는 다음의 요구사항이 추가되었을 때 반영하기가 쉽지 않다.

  • 권총을 소유하지 않은 경찰을 표현해야 한다.
  • 경찰이 권총과 수갑뿐만 아니라, 전기봉도 소유한다.

 Gun 클래스를 상속하는 Police 클래스로는 총을 소유한 경찰만 표현이 가능하고, 다른 클래스를 이용하기가 어렵다. 또한, 총을 아예 소유하지 않은 경찰을 표현하려 해도 Gun 클래스와 상속으로 묶여있기 때문에 이마저도 표현하기 어려워진다.

  하지만, HASComposite.cpp의 방식을 이용하면 전기봉을 의미하는 클래스를 만들어 해당 객체를 참조하는 멤버변수 하나만 Police에 추가하면 된다. 또한, 멤버변수 pistol을 NULL로 초기화함으로써 권총을 소유하지 않은 경찰도 표현 가능해졌다.

 반대로 HASInheritance.cpp의 방식으로 전기봉을 가진 경찰을 표현한다고 했을 때, 전기봉을 의미하는 클래스를 만들었다고 쳐도 어느 클래스에 상속시킬지가 문제가 된다. 어느 클래스에 상속시킨다고 해도 의미가 상당히 이상해진다. 

 

 따라서, 결론적으로 상속은 IS-A 관계로 표현하는 것이 매우 적절하다. 

 


출처 : 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12

728x90