지난 편에서는 @property 데코레이터를 활용해 장황한 Getter/Setter 메서드 없이도 안전하고 파이썬답게 데이터 검증 로직을 구현하는 법을 다루었습니다.
혼자 코딩할 때는 내가 만든 클래스의 구조를 머릿속으로 모두 기억하고 있으므로 큰 문제가 발생하지 않습니다. 하지만 여러 명의 팀원이 함께 프로젝트를 진행하거나, 외부 개발자가 내가 만든 시스템에 새로운 기능을 확장해서 붙여야 하는 대규모 프로젝트에서는 심각한 문제가 터지곤 합니다.
부모 클래스를 상속받아 자식 클래스를 만드는 개발자 A는 결제 메서드 이름을 pay()라고 짓고, 개발자 B는 process_payment()라고 짓고, 개발자 C는 do_pay()라고 제각각 이름을 지어버리는 상황이 대표적입니다. 이름을 통일하지 않으면 상위 시스템에서 이 객체들을 일괄적으로 제어할 때 AttributeError가 발생하며 전체 시스템이 마비됩니다.
저 역시 협업 프로젝트를 진행할 때, 동료 개발자가 필수 메서드를 구현하는 것을 빠뜨린 채 자식 클래스를 올려버리는 바람에 실서버 배포 직전에야 버그를 발견하고 식은땀을 흘렸던 경험이 있습니다. 오늘은 자식 클래스가 반드시 구현해야 할 '최소한의 약속(규격)'을 강제하여 버그를 구조적으로 차단하는 추상 클래스(ABC, Abstract Base Class)와 인터페이스 설계법을 정리해 드립니다.
1. 추상 클래스(Abstract Base Class)란 무엇인가?
추상 클래스는 미완성된 설계도라고 이해하면 쉽습니다. 자신 스스로는 완벽한 형태가 아니기 때문에 인스턴스(객체)를 직접 생성할 수 없으며, 오직 자식 클래스에게 상속되어 '뼈대' 역할을 수행합니다.
추상 메서드(
@abstractmethod): 추상 클래스 내부에서 이름과 매개변수만 정의되고, 실제 알맹이 구현 코드는 비어있는(pass) 메서드입니다.강제성: 추상 클래스를 상속받은 자식 클래스는 부모가 지정한 모든 추상 메서드를 직접 오버라이딩하여 구현하지 않으면 객체 자체를 생성할 수 없도록 파이썬 시스템이 강제합니다.
2. abc 모듈을 활용한 추상 클래스 구현 실전 예제
파이썬에서 추상 클래스를 만들려면 기본 내장 모듈인 abc(Abstract Base Classes)에서 ABC 클래스와 @abstractmethod 데코레이터를 가져와야 합니다.
다양한 결제 수단(신용카드, 카카오페이, 계좌이체 등)을 다루는 결제 시스템을 예시로 작성해 보겠습니다.
from abc import ABC, abstractmethod
# 1. 추상 클래스 정의 (ABC 상속 필수)
class PaymentProcessor(ABC):
# 자식 클래스가 반드시 구현해야 하는 추상 메서드 1
@abstractmethod
def authenticate(self):
"""사용자 인증 로직을 수행해야함"""
pass
# 자식 클래스가 반드시 구현해야 하는 추상 메서드 2
@abstractmethod
def pay(self, amount):
"""실제 결제를 진행해야 함"""
pass
# 추상 클래스도 일반 메서드를 가질 수 있음 (공통 기능 제공)
def send_receipt(self, amount):
print(f"[영수증 발송] {amount}원 결제 영수증이 이메일로 발송되었습니다.")
이제 이 설계도를 기반으로 카카오페이 결제 클래스를 구현해 보겠습니다.
# [오류 예시] 필수 추상 메서드를 하나라도 안 만들었을 때
class BadPayment(PaymentProcessor):
def authenticate(self):
print("인증 완료")
# pay() 메서드 구현을 실수로 빠뜨림!
# bad = BadPayment()
# TypeError: Can't instantiate abstract class BadPayment with abstract method pay
# 객체 생성 시점에 파이썬이 완벽하게 차단해 줍니다!
필수 메서드인 pay()를 작성하지 않은 상태로 객체를 생성하려고 하면 파이썬이 실행조차 되지 않게 TypeError를 던집니다.
# [올바른 구현 예시] 모든 추상 메서드를 정확히 구현
class KakaoPay(PaymentProcessor):
def authenticate(self):
print("[카카오페이] 사용자 생체 인증 완료")
def pay(self, amount):
print(f"[카카오페이] {amount}원 간편 결제 완료")
# 정상 객체 생성 및 실행
pay_system = KakaoPay()
pay_system.authenticate()
pay_system.pay(50000)
pay_system.send_receipt(50000)
3. 인터페이스(Interface)의 관점과 다형성(Polymorphism)의 완성
파이썬에는 Java의 interface 같은 별도의 키워드가 존재하지 않지만, 오직 추상 메서드로만 구성된 추상 클래스를 만드는 방식으로 인터페이스를 구현합니다.
인터페이스 설계를 적용하면 상위 로직을 작성할 때 개별 자식 클래스의 구체적인 내부 작동 방식을 알 필요가 없어집니다.
# 결제 승인 시스템 함수 (어떤 결제 수단이 들어오든 통일된 규칙으로 다룸)
def process_user_order(processor: PaymentProcessor, amount: int):
# 입력으로 들어온 객체가 PaymentProcessor 규격을 따르고 있는지 검증
processor.authenticate()
processor.pay(amount)
processor.send_receipt(amount)
# 신용카드, 카카오페이, 네이버페이 등 규격을 지킨 어떤 객체든 바로 통과!
process_user_order(KakaoPay(), 30000)
상위 코드에서는 "이 객체가 PaymentProcessor 규격을 지켰으니 당연히 authenticate()와 pay() 메서드를 가지고 있겠지!"라고 100% 확신하고 코드를 작성할 수 있습니다. 이것이 바로 유연하고 에러 없는 시스템 설계의 핵심 원리입니다.
4. 추상 클래스 사용 시 주의할 점
1) 남용으로 인한 과도한 복잡성 피하기
프로젝트의 규모가 작거나 혼자 작성하는 간단한 스크립트에서 모든 클래스에 추상 클래스를 적용하면, 불필요한 파일과 코드가 늘어나 오히려 가독성을 해칩니다. 팀 협업 프로젝트, 프레임워크 설계, 플러그인 구조를 구축할 때 적용하는 것이 바람직합니다.
2) 파이썬의 덕 타이핑(Duck Typing)과 균형 맞추기
파이썬은 "오리처럼 걷고 오리처럼 울면 그것은 오리다"라는 덕 타이핑 철학을 기본으로 합니다. 굳이 strict하게 상속받지 않더라도 동일한 이름의 메서드만 존재하면 동작하는 유연성이 파이썬의 장점이므로, 강한 규격 강제가 정말 필요한 핵심 모듈에만 추상 클래스를 도입하는 지혜가 필요합니다.
핵심요약
추상 클래스(ABC)는 직접 객체를 생성할 수 없으며, 자식 클래스가 상속받아 완성해야 하는 미완성 프레임워크 설계도입니다.
@abstractmethod가 붙은 메서드는 자식 클래스에서 반드시 오버라이딩하여 구현해야 하며, 미구현 시 객체 생성이 즉시 차단됩니다.추상 클래스를 활용해 인터페이스 규격을 강제하면 개발자 간 약속을 보호하고, 다형성을 활용하여 안정적이고 확장성 높은 시스템을 설계할 수 있습니다.


0 댓글