본문 바로가기
  • 쓸쓸한 개발자의 공부방
C++

[C++] 추상 클래스 (abstract class), 인터페이스(interface)

by 심찬 2021. 12. 28.

 

 

순수 가상 함수 (pure virtual function)

 - 함수의 구현부가 없고, 선언부가 =0 으로 끝나는 가상함수

 

추상 클래스 (abstract class)

- 순수 가상 함수가 한 개 이상 있는 클래스

- 객체를 생성할 수 없다.

- 포인터 변수는 만들 수 있다.

#include <iostream>

using namespace std;

class Shape
{
    public:
        virtual void Draw() = 0;
};

int main()
{
    Shape s; // error
    Shape* p;
}

 

출력 : 추상 클래스의 객체를 생성할 수 없기 때문에 에러가 발생한다.

main.cpp:21:11: error: cannot declare variable ‘s’ to be of abstract type ‘Shape’
   21 |     Shape s; // error
      |           ^
main.cpp:13:7: note:   because the following virtual functions are pure within ‘Shape’:
   13 | class Shape
      |       ^~~~~
main.cpp:16:22: note: 	‘virtual void Shape::Draw()’
   16 |         virtual void Draw() = 0;
      |                    

 

추상 클래스로 부터 파생된 클래스

- 기반 클래스의 순수 가상함수의 구현부를 제공하지 않으면 역시 추상 클래스이다.

#include <iostream>
using namespace std;

class Shape
{
    public:
        virtual void Draw() = 0;
};

class Rect : public Shape
{
    public:
        virtual void Draw() {} // 구현부
};

int main()
{
    //Shape s; // error
    Shape* p;
    
    Rect r;
}

 

추상 클래스 설계 의도

 - 파생 클래스에게 특정 멤버 함수를 반드시 만들어야 한다고 지시하는 것.

 

추상 클래스 예제

 

1. 모든 도형을 타입으로 설계 한다. =>  Rect, Circle 클래스 설계

2. 모든 도형의 공통의 기반 클래스가 있다면 모든 도형을 하나의 컨테이너에 묶어서 관리 할 수 있다.

 - Shape 클래스 도입

 - Vector<Shape*>에 모든 도형 보관

3. 모든 도형의 공통의 특징 (Draw함수) 는 반드시 Shape에 있어야 한다.

 - Shape* 를 사용해서 Draw를 호출할 수 있어야 한다.

 - 그런데 도형의 추상적인 존재이므로 그릴 수 없다.

4. Draw 를 순수 가상함수로 제공하면

 모든 도형의 설계자는 반드시Draw 함수의 구현부를 제공해야 한다.

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

class Shape
{    
public:
    virtual void Draw() = 0;
    // 모든 도형들은 반드시 이 함수를 구현부에 정의해야 한다.
    // 안전성 향상
};

class Triangle : public Shape
{
//        virtual void draw() {}
};

class Rect : public Shape
{
public:
    virtual void Draw() { cout << "Rect::Draw" << endl;}    
};
class Circle : public Shape
{
public:
    virtual void Draw() { cout << "Circle::Draw" << endl;}    
};

int main()
{
    vector<Shape*> v;

    while (1 )
    {
        int cmd;
        cin >> cmd;
        
        if      ( cmd == 1 ) v.push_back( new Rect );
        else if ( cmd == 2 ) v.push_back( new Circle );
        
        else if ( cmd == 9 )
        {
            for ( auto p : v ) // p 는 Shape* 타입
                p->Draw();
        }
    }
}

 

 

강한 결합 (tightly coupling)

객체와 다른 개체와의 관계가 강하게 연결되어 있는 것. 교체 불가능하고 확장성이 없다.

 

 


 

 

인터페이스 (Interface)

 

개방 폐쇄의 법칙 (open close principle)

 - 기능 확장 (모듈, 클래스, 함수 추가)에 열려 있고, 수정(기존 코드 수정) 에는 닫혀 있어야 한다는 원칙

 - 새로운 카메라 클래스가 추가되도 기존 클래스의 코드를 수정하지 않도록 만들어야 한다.

 

#include <iostream>

class Camera
{
public:
    void take() 
    {
         std::cout << "take picture" << std::endl; 
    }
};

class HDCamera
{
public:
    void take() 
    {
         std::cout << "take picture HD" << std::endl; 
    }
};

class People
{
public:    
    void useCamera(Camera* p) { p->take();}
    void useCamera(HDCamera* p) { p->take();}
};

int main()
{
    People p;
    Camera c1;
    p.useCamera(&c1);
    
    HDCamera hd;
    p.useCamera(&hd);
}

 

 

계약에 의한 설계

 - 사람과 카메라 제작자 사이에 지켜야 하는 규칙을 먼저 설계

 - 규칙은 추상 클래스를 사용하여 설계한다.

규칙

 - 모든 카메라는 ICamera 로 부터 파생되어야 한다.

카메라 사용자

 - 규칙대로만 사용하면 된다.

모든 카메라 제작자

 - 반드시 규칙을 지켜야 한다.

 

 

#include <iostream>

// 인터페이스
struct ICamera   // struct!!!
{
    virtual void take() = 0;
};

class People
{
public:    
    void useCamera(ICamera* p) { p->take();}
};

class Camera : public ICamera
{
public:
    void take() 
    {
         std::cout << "take picture" << std::endl; 
    }
};

class HDCamera : public ICamera
{
public:
    void take() 
    {
         std::cout << "take picture hd" << std::endl; 
    }
};

int main()
{
    People p;
    Camera c1;
    p.useCamera(&c1);
    
    HDCamera c2;
    p.useCamera(&c2);

}

 

 

약한 결합 (loosely coupling)

 

객체와 다른 객체와의 관계가 약하게 연결되어 있는 것 (인터페이스를 사용해서 통신), 교체 가능하고 확장성이 좋다.

 

 

댓글