[Design Pattern] 추상(Abstract) 에 대하여
추상(Abstract)이란?
“추상 패턴”이라는 용어는 디자인 패턴 분야에서 명확하게 정의된 용어는 아닙니다.
하지만, “추상화(Abstraction)”라는 개념은 객체 지향 프로그래밍 뿐만 아니라 디자인 패턴 전반에 걸쳐 매우 중요한 역할을 하기 때문에
한번에 묶어서 객체 지향 프로그래밍에서의 추상화와 관련된 주요 디자인 패턴들을 함께 설명드리겠습니다.
추상화(Abstraction)의 정의
추상화는 복잡한 시스템의 핵심적인 부분에 집중하고 불필요한 세부 사항을 숨기는 과정입니다. 객체 지향 프로그래밍에서는 추상 클래스(Abstract Class)와 인터페이스(Interface)를 통해 구현됩니다.
- 추상 클래스: 하나 이상의 추상 메서드(구현이 없는 메서드)를 포함할 수 있는 클래스입니다. 추상 클래스는 직접 객체를 생성할 수 없으며, 반드시 다른 클래스가 상속받아 추상 메서드를 구현해야 합니다.
- 인터페이스: 모든 메서드가 추상 메서드인 클래스입니다 (C++에서는 순수 가상 함수로 구현). 인터페이스는 클래스가 특정 기능을 제공하기 위한 규약을 정의합니다.
추상(Abstract)과 관련된 주요 디자인 패턴
추상화는 여러 디자인 패턴의 핵심 원리로 사용됩니다. 그 중에서도 특히 중요한 패턴들은 다음과 같습니다.
- 추상 팩토리 패턴(Abstract Factory Pattern): 관련 있는 객체들을 생성하기 위한 인터페이스를 제공하는 패턴입니다. 구체적인 클래스를 지정하지 않고 객체들을 생성할 수 있도록 해줍니다.
- 팩토리 메서드 패턴(Factory Method Pattern): 객체 생성을 서브클래스로 위임하는 패턴입니다. 어떤 클래스의 객체를 생성할지는 서브클래스에서 결정합니다.
- 템플릿 메서드 패턴(Template Method Pattern): 알고리즘의 뼈대를 정의하고, 일부 단계를 서브클래스에서 구현하도록 하는 패턴입니다. 코드의 중복을 줄이고 알고리즘의 구조를 유지하는 데 유용합니다.
- 전략 패턴(Strategy Pattern): 알고리즘 군을 정의하고, 각각을 캡슐화하여 필요에 따라 교체할 수 있도록 하는 패턴입니다. 알고리즘의 변경이 용이하도록 해줍니다.
추상(Abstract)의 장단점
장점
- 코드의 유연성 및 확장성 향상: 구체적인 구현에 의존하지 않고 추상적인 인터페이스에 의존함으로써, 코드를 수정하지 않고도 다양한 구현을 사용할 수 있습니다.
- 코드의 재사용성 향상: 추상 클래스나 인터페이스를 통해 공통적인 부분을 정의함으로써, 코드 중복을 줄이고 재사용성을 높일 수 있습니다.
- 코드의 가독성 및 유지보수성 향상: 복잡한 시스템을 단순화하여 이해하기 쉽게 만들고, 유지보수성을 향상시킵니다.
단점
- 과도한 추상화로 인한 복잡성 증가 : 너무 많은 추상화 계층을 도입하면 코드의 흐름을 파악하기 어려워지고, 디버깅이 힘들어질 수 있습니다. 각 추상 계층을 이해하고 연결해야 전체적인 동작을 이해할 수 있기 때문입니다.
- 성능 저하 가능성 : 추상 메서드나 인터페이스를 사용하는 경우, 컴파일 시점에 어떤 메서드가 호출될지 결정되지 않고 실행 시간에 결정되는 동적 바인딩(dynamic binding)이 발생합니다. 이는 직접 호출보다 약간의 성능 오버헤드를 발생시킬 수 있습니다. 특히, 매우 빈번하게 호출되는 부분에서는 이러한 오버헤드가 누적되어 성능에 영향을 미칠 수 있습니다.
- 상위 클래스의 변경에 따른 하위 클래스의 영향: 추상 클래스를 상속받는 경우, 상위 클래스의 변경이 하위 클래스에 영향을 미칠 수 있습니다. 특히, 상위 클래스의 추상 메서드가 변경되면 모든 하위 클래스에서 해당 메서드를 다시 구현해야 합니다. 이는 캡슐화를 약화시키고 코드의 의존성을 높이는 결과를 초래할 수 있습니다. 이러한 이유로 “상속보다는 구성(composition)을 사용하라”는 객체 지향 설계 원칙이 강조되기도 합니다.
예제
#include <iostream>
class Shape { // 추상 클래스
public:
virtual void draw() = 0; // 순수 가상 함수 (추상 메서드)
virtual ~Shape() = default; // 가상 소멸자 (다형성을 위해 필요)
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing a circle\n";
}
};
class Square : public Shape {
public:
void draw() override {
std::cout << "Drawing a square\n";
}
};
int main() {
// Shape shape = new Shape(); // 오류: 추상 클래스는 객체를 생성할 수 없음
Shape* circle = new Circle();
Shape* square = new Square();
circle->draw(); // Drawing a circle
square->draw(); // Drawing a square
delete circle;
delete square;
return 0;
}