Programer Story/Remember Story!!!!!

인터페이스와 추상 클래스

무등산보리밥 2011. 5. 28. 23:52

1, 추상 클래스

 추상 클래스(abstract class)는 객체를 생성할 수 없는 객체들의 공통된 개념만을 표현하기 위한 클래스입니다.

클래스는 객체 생성 여부에 따라 두 가지로 나눌 수 있습니다.

  • 추상 클래스(abstract class) : 인스턴스를 만들 수 없는 클래스
  • 구체화 클래스(concrete class) : 인스턴스를 만들 수 있는 클래스

 

 예로 스타크래프트 게임의 유닛을 클래스로 표현하면 아래 그림과 같이 표현할 수 있습니다. 스타크래프트 게임에서 '히드라'나 '질럿'이나 '마린'객체는 실제 만들어지는 객체이며 '유닛'이라는 객체는 만들어지지 않습니다. '유닛'은 단지 유닛들의 공통된 개념만을 표현(추상화)합니다.

'유닛 클래스'처럼 객체들의 공통된 개념만을 표현하기 위한 클래스가 추상클래스입니다.

C++문법은  '순수 가상 함수'를 가지는 클래스를 추상 클래스라 합니다. 자바나 C# 처럼 클래스를 추상 클래스로 만드는 키워드(abstract)는 존재하지 않습니다.

순수 가상 함수는 함수의 몸체를 가지지 않는 함수입니다.

형식은 virtual void Move() = 0; 입니다.

그래서 순수 가상 함수는 자식 클래스에서 필수적으로 구현해야 합니다. 구현하지 않으면 컴파일러 에러가 발생합니다.

 

 아래와 같이 Unit 클래스가 하나 이상의 순수 가상함수를 가지면 추상클래스가 되어 객체를 생성할 수 없습니다.

#include <iostream>
using namespace std;

class Unit
{
    //**유닛의 공통된 속성과 행동 정의** ...
public:
    // 모든 유닛은 이동 가능합니다.
    virtual void Move( ) = 0; // 순수 가상 함수
};
class Hydra : public Unit
{
public:
    void Move( ) { cout << "히드라 이동" << endl; }// 순수 가상 함수 구현!
};
class Zealot : public Unit
{
public:
    void Move( ) { cout << "질럿 이동" << endl; }// 순수 가상 함수 구현!
};
class Marine : public Unit
{
public:
    void Move( ) { cout << "마린 이동" << endl; }// 순수 가상 함수 구현!
};
void main( )
{
    Unit unit; // 에러~! Unit 클래스는 추상클래스로 객체를 생성할 수 없습니다.

}
  1. 에러~

 Unit 클래스는 추상 클래스로 객체를 생성할 수 없으므로 컴파일러 에러입니다.

또 순수 가상 함수는 자식 클래스(구체화 클래스)에서 꼭 구현해야합니다.


우리는 앞장에서 '다형성'을 공부했습니다. 기억이 가물가물하다면 다시 "객체지향"과 "다형성과 가상함수" 장을 공부하세요.

추상 클래스는 객체들의 공통된 개념이며 순수 가상 함수를 자식 클래스(구체화 클래스)들이 꼭 구현해야 하므로 사용자는 이 추상 클래스 인터페이스만 알고 있다면 이 상속 계층 구조를 따르는 모든 클래스의 객체들을 하나의 인터페이스로 다룰 수 있습니다.

 

그래서 추상 클래스의 포인터를 이용하여 모든 자식 클래스를 동일한 인터페이스로 다루게 됩니다.

#include <iostream>
using namespace std;
class Unit
{
    //**유닛의 공통된 속성과 행동 정의** ...
public:
    // 모든 유닛은 이동 가능합니다.
    virtual void Move( ) = 0; // 순수 가상함수
};
class Hydra : public Unit
{
public:
    void Move( ) { cout << "히드라 이동" << endl; }
};
class Zealot : public Unit
{
public:
    void Move( ) { cout << "질럿 이동" << endl; }
};
class Marine : public Unit
{
public:
    void Move( ) { cout << "마린 이동" << endl; }
};
void main( )
{
    Unit *pUnit1=new Hydra, *pUnit2=new Zealot, *pUnit3=new Marine;
    Unit *arrUnit[3] = { new Hydra, new Zealot, new Marine};

    //서로 다른 객체이지만 모두 동일한 인터페이스로 다룰 수 있습니다.
    pUnit1->Move();
    pUnit2->Move();
    pUnit3->Move();
    arrUnit[0]->Move();
    arrUnit[1]->Move();
    arrUnit[2]->Move();
}

히드라 이동
질럿 이동
마린 이동
히드라 이동
질럿 이동
마린 이동

 모든 Unit객체는 Move()라는 인터페이스를 가지고 있으므로 '히드라', '질럿', '마린' 모두 Unit 클래스의 Move() 메소드를 사용할 수 있으며 다형성을 이용하여 실제 객체의 Move() 메소드가 정확히 호출되는 것을 볼 수 있습니다.

 

정리하자면

  • 순수 가상 함수는 모든 자식 클래스들이 이(순수 가상 함수) 인터페이스를 갖도록 '강제'하는 것입니다.
  • 추상 클래스는 순수 가상 함수를 하나 이상 포함하는 클래스로 객체를 생성할 수 없고 객체들의 공통 개념을 표현하고 인터페이스로만 이용되는 클래스입니다.

 

2, 인터페이스

인터페이스는 앞장의 "객체지향"을 참고하세요.

  '클래스의 범위'에서 간단하게 말하면 인터페이스는 공개 메소드의 시그니처( 함수의 이름, 매개변수 자료형, 리턴 자료형)입니다.

단지 공개 메소드의 시그니처만 알고 있으면 객체의 기능을 사용(수행)할 수 있습니다.


1. 추상 클래스를 이용한 유닛의 이동과 공격!

 #include <iostream>
using namespace std;

class Unit
{
    //**유닛의 공통된 여러 속성과 행동 정의** ...
public:
    // 모든 유닛은 이동, 공격 가능합니다.
    virtual void Move( ) = 0;
    virtual void Attack( ) = 0;
};
class Hydra : public Unit
{
public:
    void Move( ) { cout << "히드라 이동" << endl; }
    void Attack( ) { cout << "히드라 공격!" << endl; }
};
class Zealot : public Unit
{
public:
    void Move( ) { cout << "질럿 이동" << endl; }
    void Attack( ) { cout << "질럿 공격!" << endl; }
};
class Marine : public Unit
{
public:
    void Move( ) { cout << "마린 이동" << endl; }
    void Attack( ) { cout << "질럿 공격!" << endl; }
};
void main( )
{
    Unit *pUnit = new Hydra;

    //서로 다른 객체이지만 모두 동일한 인터페이스로 다룰 수 있습니다.

    pUnit->Move();
    pUnit->Attack();   

 

    pUnit = new Zealot;
    pUnit->Move();
    pUnit->Attack();   

}

  1. 히드라 이동
    히드라 공격!
    질럿 이동
    질럿 공격!

추상 클래스는 객체들의 공통된 인터페이스를 가지고 있으므로 모든 객체의 공통된 기능을 사용할 수 있습니다. 그러나 만약 사용자가 '이동'이나 '공격'이라는 기능에만 관심이 있는 경우에도 유닛의 정보를 사용자가 알아야 한다는 문제가 있습니다. 

그래서 인터페이스(행동,기능)만 알아도 사용할 수 있도록 아래와 같이 구성할 수 있습니다.




이것을 C++문법으로 구현하려면...

 

 인터페이스는 공개 메소드들의 시그니처 집합이라 할 수 있습니다. 그래서 IMovable 클래스가 인터페이스입니다. 요놈을 구현한 모든 Move()를 수행할 수 있습니다. JAVA나 C#은 interface를 언어 차원에서 제공하지만 C++문법은 interface를 문법적으로 지원하지 않기 때문에 interface의 개념을 struct나 class 키워드를 사용하여 구현합니다.

즉, C++에서 인터페이스는 public 순수 가상 함수로만 만들어진 클래스로 구현할 수 있습니다.

 

 

위 내용을 코드로 표현하면 아래와 같습니다.

#include <iostream>
using namespace std;

struct IMovable
{
    virtual void Move( ) = 0;
};
class Unit : public IMovable
{
    //**유닛의 공통된 여러 속성과 행동 정의** ...
public:
    // 모든 유닛은 이동, 공격 가능합니다.
    virtual void Attack( ) = 0;
};
class Hydra : public Unit
{
public:
    void Move( ) { cout << "히드라 이동" << endl; }
    void Attack( ) { cout << "히드라 공격!" << endl; }
};
class Zealot : public Unit
{
public:
    void Move( ) { cout << "질럿 이동" << endl; }
    void Attack( ) { cout << "질럿 공격!" << endl; }
};
class Marine : public Unit
{
public:
    void Move( ) { cout << "마린 이동" << endl; }
    void Attack( ) { cout << "질럿 공격!" << endl; }
};
void main( )
{
    Unit *pUnit = new Hydra;
    // Unit 정보를 알아야 사용할 수 있습니다.
    pUnit->Move();
    pUnit->Attack();
   
    cout << "==============" << endl;

    // 이동 기능 인터페이스만 알아도 사용할 수 있습니다.

    IMovable *pMoveInterface = pUnit;
    pMoveInterface->Move();
}

  1. 히드라 이동
    히드라 공격!
    ==============
    히드라 이동

 IMovable 인터페이스만 알면 어떤 객체를 가리키는지 알지 못해도 Move() 메소드를 사용할 수 있습니다.

 

Person 클래스도 '이동' 기능을 가지고 있으므로 IMovable 인터페이스를 구현하면 '유닛'인지 '사람'인지 알 필요 없이 모두 사용 가능합니다.

 #include <iostream>
#include <string.h>
using namespace std;

// 이동 기능의 인터페이스 정의
struct IMovable
{
    virtual void Move( ) = 0;
};
// 사람 클래스 정의
class Person : public IMovable
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)   
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   cout << "eat!!!" << endl; }
    virtual void Print( ) const
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
    const char* GetName( ) const { return name; }
    int GetAge( ) const { return age; }
    void Move( ) { cout << "사람이 이동합니다." << endl; }
};
class Student : public Person
{
    int grade;
public:
    Student(const char* n, int a, int g):Person(n,a), grade(g) {    }
    void Study( ) { cout << "study!!!" << endl; }
    void Print( ) const
    {
        cout << "name : " << GetName( ) <<", " <<"age : " << GetAge() <<", ";
        cout << "grade : " << grade << endl;
    }
    int GetGrade( ) const { return grade; }
};
class Professor : public Person
{
    char position[20];
public:
    Professor(const char* n, int a, const char* p):Person(n,a)
    {
        strcpy(position, p);
    }
    void Teach( ) { cout << "teach!!!" << endl; }
    void Print( ) const
    {
        cout << "name : " << GetName( ) <<", " <<"age : " << GetAge() <<", ";
        cout << "position : " << position << endl;
    }
    const char* GetPosition( ) const { return position; }
};

// 유닛 클래스 정의
class Unit : public IMovable
{
    //**유닛의 공통된 여러 속성과 행동 정의** ...
public:
    // 모든 유닛은 이동, 공격 가능합니다.
    virtual void Attack( ) = 0;
};
class Hydra : public Unit
{
public:
    void Move( ) { cout << "히드라 이동" << endl; }
    void Attack( ) { cout << "히드라 공격!" << endl; }
};
class Zealot : public Unit
{
public:
    void Move( ) { cout << "질럿 이동" << endl; }
    void Attack( ) { cout << "질럿 공격!" << endl; }
};
class Marine : public Unit
{
public:
    void Move( ) { cout << "마린 이동" << endl; }
    void Attack( ) { cout << "질럿 공격!" << endl; }
};
void main( )
{
    // 사용자는 이동 인터페이스만 알아도 사용할 수 있습니다.
    IMovable *pMoveInterface = NULL;
   
    pMoveInterface = new Zealot;
    pMoveInterface->Move();
    pMoveInterface = new Student("김학생", 20, 1);
    pMoveInterface->Move();
    pMoveInterface = new Marine;
    pMoveInterface->Move();
    pMoveInterface = new Professor("김교수", 50, "부교수");
    pMoveInterface->Move();
    pMoveInterface = new Hydra;
    pMoveInterface->Move();
}
  1. 질럿 이동
    사람이 이동합니다.
    마린 이동
    사람이 이동합니다.
    히드라 이동

설명은 아래 그림으로 ...

 

'유닛'과 '사람'처럼 개념적으로 상관없을 객체들도 인터페이스(IMovable)를 이용하여 동일한 인터페이스(Move())를 사용할 수 있습니다.

즉, 어떤 객체인지 상관없이 IMovable 인터페이스를 지원하는 객체는 Move() 기능을 가지며 사용될 수 있다는 것입니다.



만약, Person 클래스도 인스턴스를 생성하지 않는 추상클래스라면 코드가 아래와 같이 만들어집니다.

 #include <iostream>
#include <string.h>
using namespace std;

// 이동 기능의 인터페이스 정의
struct IMovable
{
    virtual void Move( ) const = 0;
};
// 사람 클래스 정의
class Person : public IMovable
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)   
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) const { cout << "eat!!!" << endl; }
    virtual void Print( ) const = 0;
    const char* GetName( ) const { return name; }
    int GetAge( ) const { return age; }
    void Move( ) const { cout << "사람이 이동합니다." << endl; }
};
class Student : public Person
{
    int grade;
public:
    Student(const char* n, int a, int g):Person(n,a), grade(g) {    }
    void Study( ) const { cout << "study!!!" << endl; }
    void Print( ) const
    {
        cout << "name : " << GetName( ) <<", " <<"age : " << GetAge() <<", ";
        cout << "grade : " << grade << endl;
    }
    int GetGrade( ) const { return grade; }
};
class Professor : public Person
{
    char position[20];
public:
    Professor(const char* n, int a, const char* p):Person(n,a)
    {
        strcpy(position, p);
    }
    void Teach( ) const { cout << "teach!!!" << endl; }
    void Print( ) const
    {
        cout << "name : " << GetName( ) <<", " <<"age : " << GetAge() <<", ";
        cout << "position : " << position << endl;
    }
    const char* GetPosition( ) const { return position; }
};

// 유닛 클래스 정의
class Unit : public IMovable
{
    //**유닛의 공통된 여러 속성과 행동 정의** ...
public:
    // 모든 유닛은 이동, 공격 가능합니다.
    virtual void Attack( )const = 0;
};
class Hydra : public Unit
{
public:
    void Move( ) const { cout << "히드라 이동" << endl; }
    void Attack( ) const{ cout << "히드라 공격!" << endl; }
};
class Zealot : public Unit
{
public:
    void Move( ) const { cout << "질럿 이동" << endl; }
    void Attack( ) const { cout << "질럿 공격!" << endl; }
};
class Marine : public Unit
{
public:
    void Move( ) const { cout << "마린 이동" << endl; }
    void Attack( ) const { cout << "질럿 공격!" << endl; }
};
void main( )
{
    // 이동 인터페이스만 알아도 사용할 수 있습니다.
    IMovable *pMoveInterface = NULL;

    pMoveInterface = new Zealot;
    pMoveInterface->Move();
    pMoveInterface = new Student("김학생", 20, 1);
    pMoveInterface->Move();
    pMoveInterface = new Marine;
    pMoveInterface->Move();
    pMoveInterface = new Professor("김교수", 50, "부교수");
    pMoveInterface->Move();
    pMoveInterface = new Hydra;
    pMoveInterface->Move();
}

  1. 질럿 이동
    사람이 이동합니다.
    마린 이동
    사람이 이동합니다.
    히드라 이동

모든 Person이 Print() 인터페이스를 가져야 한다는 것을 강제하기 위해 Print() 함수가 '순수 가상 함수'입니다. 그래서 Person 클래스는 추상 클래스입니다.

나머지 설명은 아래 그림으로 ...