C++ 인터페이스란 무엇인가?
인터페이스(interface)는 C++ 클래스가 제공할 동작이나 기능을 정의하면서도, 그 구체적인 구현 방식에는 얽매이지 않도록 하는 역할을 합니다. 즉, "무엇을 할 수 있는가"만 약속하고 "어떻게 할 것인가"는 구현체에 맡기는 설계 도구입니다.
C++에서는 추상 클래스(abstract class)를 사용해 인터페이스를 구현합니다. 단, 여기서 말하는 추상 클래스는 구현 세부 사항과 데이터를 분리하는 개념인 '데이터 추상화(data abstraction)'와는 다른 개념이므로 혼동하지 않아야 합니다.
순수 가상 함수로 추상 클래스 만들기
클래스의 멤버 함수 중 최소 하나 이상을 순수 가상 함수(pure virtual function)로 선언하면 그 클래스는 추상 클래스가 됩니다. 순수 가상 함수는 함수 선언 끝에 = 0을 붙여 지정합니다.
class Box {
public:
// 순수 가상 함수
virtual double getVolume() = 0;
private:
double length; // 상자의 길이
double breadth; // 상자의 너비
double height; // 상자의 높이
};
추상 클래스(흔히 ABC, Abstract Base Class라고 부릅니다)의 목적은 다른 클래스들이 상속받아 사용할 수 있는 적절한 기반 클래스를 제공하는 것입니다. 추상 클래스는 객체 생성에 사용할 수 없으며 오직 인터페이스로만 존재합니다. 추상 클래스의 객체를 생성하려고 시도하면 컴파일 오류가 발생합니다.
따라서 추상 클래스의 파생 클래스를 실제로 인스턴스화하려면, 부모가 선언한 모든 순수 가상 함수를 반드시 재정의(오버라이드)해야 합니다. 이는 곧 해당 파생 클래스가 추상 클래스가 선언한 인터페이스를 지원한다는 의미입니다. 파생 클래스에서 순수 가상 함수를 오버라이드하지 않은 채 객체를 생성하려 하면 역시 컴파일 오류가 발생합니다.
반면, 객체 생성이 가능한 일반 클래스는 구체 클래스(concrete class)라고 부릅니다.
예제 코드
다음 예제는 부모 클래스인 Shape가 getArea()라는 함수의 인터페이스를 제공하고, 이를 상속받은 자식 클래스들이 각자의 방식으로 구현하는 모습을 보여줍니다.
#include <iostream>
using namespace std;
class Shape { // 기반 클래스
public:
// 인터페이스의 틀을 제공하는 순수 가상 함수
virtual int getArea() = 0;
void setWidth(int w) {
width = w;
}
void setHeight(int h) {
height = h;
}
protected:
int width;
int height;
};
class Rectangle : public Shape { // 파생 클래스
public:
int getArea() {
return (width * height);
}
};
class Triangle : public Shape {
public:
int getArea() {
return (width * height) / 2;
}
};
int main(void) {
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
// 객체의 면적 출력
cout << "Total Rectangle area: " << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
// 객체의 면적 출력
cout << "Total Triangle area: " << Tri.getArea() << endl;
return 0;
}
실행 결과
Total Rectangle area: 35
Total Triangle area: 17
위 예제에서 볼 수 있듯이, 추상 클래스 Shape는 getArea()를 통해 인터페이스를 정의하고, 두 파생 클래스는 같은 함수를 서로 다른 알고리즘으로 구현해 각 도형에 맞는 면적을 계산합니다.
설계 전략
객체지향 시스템은 추상 기반 클래스를 활용해 외부 애플리케이션 모두에 공통적으로 적용되는 표준화된 인터페이스를 제공할 수 있습니다. 이후 이 추상 기반 클래스를 상속함으로써, 비슷한 방식으로 동작하는 파생 클래스들을 만들어 나갑니다.
외부 애플리케이션에 노출되는 기능(즉, public 함수들)은 추상 기반 클래스 안에서 순수 가상 함수 형태로 정의됩니다. 그리고 이 순수 가상 함수들의 실제 구현은, 애플리케이션의 구체적인 유형에 대응하는 파생 클래스에서 제공됩니다.
이러한 아키텍처의 큰 장점은 시스템이 한 번 정의된 이후에도 새로운 애플리케이션을 손쉽게 추가할 수 있다는 점입니다. 새 도형 타입이 필요하다면 Shape를 상속해 getArea()만 구현하면 되기 때문입니다.
핵심 요약
- 순수 가상 함수(
= 0)가 하나라도 있으면 그 클래스는 추상 클래스가 됩니다. - 추상 클래스는 객체 생성이 불가능하며, 인터페이스 역할만 수행합니다.
- 파생 클래스는 모든 순수 가상 함수를 반드시 오버라이드해야 인스턴스화할 수 있습니다.
- 객체 생성이 가능한 클래스를 구체 클래스(concrete class)라고 부릅니다.
- 추상 기반 클래스를 활용하면 확장성 있고 표준화된 시스템 설계가 가능합니다.