
객체 지향 프로그래밍(OOP)을 수행할 때, 우리는 종종 '특정 기능을 가진 객체'를 규정해야 하는 상황에 직면합니다. 파이썬은 이를 위해 두 가지 강력한 도구를 제공합니다. 바로 추상 베이스 클래스(Abstract Base Classes, ABC)와 프로토콜(Protocol, Structural Typing)입니다. 이 글에서는 숙련된 파이썬 개발자의 관점에서 이 두 개념의 본질적인 차이를 분석하고, 실무에서 마주하는 설계 결합도 문제를 해결하는 최무의 가이드를 제시합니다.
1. 명시적 상속(Nominal) vs 구조적 타이핑(Structural)
파이썬의 타입 시스템은 시간이 흐름에 따라 진화해 왔습니다. abc 모듈을 통한 ABC 방식이 "나는 이 가문의 자손이다"라고 증명하는 명시적 상속 기반이라면, typing.Protocol은 "오리처럼 걷고 오리처럼 울면 오리다"라는 덕 타이핑(Duck Typing)의 정적 버전입니다.
추상 베이스 클래스 (ABC)
ABC는 isinstance()와 issubclass() 검사를 통과하기 위해 명시적으로 부모 클래스를 상속받아야 합니다. 이는 강력한 계층 구조를 형성하며, 설계 단계에서 '엄격한 계약'을 맺는 것과 같습니다.
프로토콜 (Protocol)
반면, 프로토콜은 상속 관계가 없더라도 클래스가 특정 메서드나 속성을 가지고 있기만 하면 해당 타입으로 인정합니다. 이를 통해 타사 라이브러리나 수정할 수 없는 기존 코드에 대해서도 유연하게 인터페이스를 정의할 수 있습니다.
2. ABC와 Protocol의 주요 차이점 비교
두 방식의 특징을 한눈에 파악할 수 있도록 상세 비교표를 작성하였습니다.
| 비교 항목 | 추상 베이스 클래스 (ABC) | 프로토콜 (Protocol) |
|---|---|---|
| 타이핑 방식 | 명시적 타이핑 (Nominal Typing) | 구조적 타이핑 (Structural Typing) |
| 상속 필수 여부 | 반드시 상속받아야 함 (Inheritance) | 메서드 구현만으로 충분 (Implementation) |
| 도입 시점 | Python 2.6 / 3.0 (PEP 3119) | Python 3.8 (PEP 544) |
| 주요 목적 | 런타임 객체 설계 및 코드 재사용 | 정적 타입 검사 및 유연한 결합도 유지 |
| 제약 사항 | 다중 상속 시 계층 구조가 복잡해짐 | 런타임 오버헤드가 발생할 수 있음 (runtime_checkable 사용 시) |
3. 실무에서의 문제 해결: 언제 무엇을 쓸 것인가?
설계 과정에서 발생하는 복잡성을 줄이기 위해 다음과 같은 기준을 권장합니다.
- ABC를 사용해야 할 때: 내부 프레임워크를 설계하거나, 공통된 기본 로직(Base Implementation)을 하위 클래스에 전달하여 코드 중복을 막고 싶을 때 적합합니다.
- Protocol을 사용해야 할 때: 외부 라이브러리 의존성을 줄이고 싶거나, 이미 정의된 여러 클래스에 공통 인터페이스를 사후에 부여하고 싶을 때(Retroactive Typing) 최상의 선택입니다.
4. Sample Example: 인터페이스 구현 및 해결 방법
다음은 메시지를 전송하는 기능을 구현할 때 두 방식이 어떻게 다른지 보여주는 코드 예제입니다.
ABC 방식 (상속 필요)
from abc import ABC, abstractmethod
class MessageSender(ABC):
@abstractmethod
def send(self, message: str) -> None:
pass
class EmailService(MessageSender):
def send(self, message: str) -> None:
print(f"Sending Email: {message}")
Protocol 방식 (상속 불필요)
from typing import Protocol
class Sender(Protocol):
def send(self, message: str) -> None:
...
class SMSService: # 상속받지 않음
def send(self, message: str) -> None:
print(f"Sending SMS: {message}")
def notify(service: Sender, msg: str):
service.send(msg)
# SMSService는 Sender 프로토콜을 만족하므로 정상 작동함
notify(SMSService(), "Hello Protocol!")
5. 결론 및 요약
파이썬 3.8 이상을 사용하는 현대적인 개발 환경에서는 Protocol을 우선적으로 고려하는 것이 설계의 유연성을 확보하는 지름길입니다. 하지만 대규모 시스템에서 명확한 계층 구조와 기본 기능 제공이 필요하다면 ABC는 여전히 대체 불가능한 도구입니다. 이 2가지 도구의 특성을 명확히 이해하고 혼합하여 사용한다면, 유지보수가 쉬운 견고한 코드를 작성할 수 있습니다.
6. 출처 및 참고 문헌
- Python Documentation:
abc— Abstract Base Classes (https://docs.python.org/3/library/abc.html) - PEP 544 – Protocols: Structural subtyping (https://peps.python.org/pep-0544/)
- Fluent Python by Luciano Ramalho (O'Reilly Media)
- Real Python: Python's abc Module and Protocol (https://realpython.com)