인터페이스 분리 원칙(ISP)
- 정의
- 인터페이스 분리 원칙(Interface Segregation Principle, ISP) 은 “클라이언트는 자신이 사용하지 않는 메서드에 의존해서는 안 된다” 는 원칙
- 하나의 큰(fat) 인터페이스를 여러 개의 작고 응집력 높은 인터페이스로 분리하고
- 클래스는 필요한 인터페이스만 구현하도록 강제하는 것.
- 객체지향에서 인터페이스의 본질
- 인터페이스 = 객체가 외부에 노출하는 메서드의 집합(메시지 프로토콜)
- 클래스는 “구현”, 인터페이스는 “행동의 추상화”
- 클라이언트는 인터페이스를 통해 객체와 상호작용함.
- Python에서의 인터페이스 정의 방식
- 예전: 덕 타이핑(Duck Typing)
- “어떤 새가 오리처럼 걷고 오리처럼 꽥꽥 소리를 내면 그것은 오리다.”
- 타입 이름, docstring, 속성 이름에 상관없이 실제로 가지고 있는 메서드가 객체의 정체성을 결정함.
- 현재: 추상 기본 클래스(Abstract Base Class, ABC) 가 도입(Python 3(PEP-3119)부터)
- 파생 클래스가 구현해야 할 일부분을 공통된 기본 동작으로 구현하거나 인터페이스로 정의하는 것
- 특정 중요 메서드가 실제로 재정의 되었는지 확인이 필요할 때 유용,
isinstance()와 같은 메서드의 기능을 재정의하거나 확장하는 메커니즘으로도 작동함.
- 예전: 덕 타이핑(Duck Typing)
c.f.) ABC의 register() 메서드를 사용하면 기존 기본 클래스에 파생 클래스를 추가 할 수 있다. 이렇게 추가된 파생 클래스를 가상 서브 클래스(virtual subclass)라고 한다. (ex. 오리와 같이 걷고, 오리와 같이 소리를 내고, 자신을 오리라고 말할 수 있는 새로운 유형의 오리를 등록하는 것)
| 구분 | 설명 | 특징 |
|---|---|---|
| Duck Typing | 메서드 존재 여부로 인터페이스 판단 | 런타임 동적 검사 |
| ABC + @abstractmethod | 명시적으로 구현해야 할 메서드를 강제 | 인스턴스화 시점에 검사 |
| Virtual Subclass | ABC.register()로 기존 클래스를 계층에 등록 |
상속 없이 인터페이스 만족 |
@abstractmethod: 기본 클래스에서 이 데코레이터로 마킹한 메서드는 파생 클래스에서 모두 구현해야만 인스턴스화가 가능함.@abstractmethod가 붙은 메서드가 구현되지 않으면__new__단계에서TypeError를 발생시킴
- 가상 서브클래스(Virtual Subclass):
abc모듈의register()메서드를 사용하면 기존 기본 클래스에 파생 클래스를 계층 구조의 일부로 등록할 수 있음.- Virtual subclass는
__subclasscheck__/__instancecheck__를 오버라이드하여isinstance(),issubclass()의 동작을 확장한다.
- Virtual subclass는
재사용성을 높이기 위해 가능한 작은 단위로 인터페이스를 분리한다면 인터페이스를 구현하려는 각 클래스가 매우 명확한 동작과 책임을 지니기 때문에 응집력이 높아진다.
너무 많은 일을 하는 인터페이스
나쁜 예: 모든 기능을 한 인터페이스에 몰아넣음
class EventParser: # 그림 4.7
def from_xml(self): ...
def from_json(self): ...

문제점
- XML만 필요한 클래스도 JSON 메서드를 강제로 구현해야 함
- 결합력(Coupling) 증가, 유연성 감소
- 클라이언트가 사용하지 않는 메서드에 의존 → ISP 위반
- SRP도 함께 위반 (변경 이유가 두 가지 이상이 됨)
인터페이스는 작을수록 좋다
- 위의 예시에서 각각 하나의 메서드를 가진 두 개의 다른 인터페이스로 분리하는 것이 좋다.
- 이렇게 하면
XMLEventParser에서 파생된 클래스는from_xml()메서드만을 구현하면 되고, 마찬가지로JSONEventParser에서 파생된 클래스는from_json()메서드만을 구현하면 됨.

장점
- 각 인터페이스의 응집력(Cohesion) 극대화
- 필요한 기능만 구현 → 클래스 책임이 명확해짐
- 다중 상속을 통해 필요한 조합을 자유롭게 구성 가능
- 새로운 파서(예: YAML)를 추가할 때 기존 코드 수정 불필요 (OCP와 연계)
그림 4.8 구현코드)
from abc import ABCMeta, abstractmethod
class XMLEventParser(metaclass=ABCMeta):
@abstractmethod
def from_xml(self, xml_data: str):
"""XML 형태의 데이터를 파싱"""
class JSONEventParser(metaclass=ABCMeta):
@abstractmethod
def from_json(self, json_data: str):
"""JSON 형태의 데이터를 파싱"""
class EventParser(XMLEventParser, JSONEventParser):
"""XML과 JSON 형태를 모두 지원"""
def from_xml(self, xml_data: str):
pass
def from_json(self, json_data: str):
pass
- 주의: 인터페이스에서 정의한 추상 메서드는 구체 클래스에서 반드시 구현해야 한다는 점. 추상메서드를 구현하지 않으면 런타임 오류가 발생함.
- SRP와 유사하지만, ISP는 '행동의 추상화(인터페이스)'에 초점을 맞추고 있음.
- ISP를 준수하지 않아 별개의 기능이 결합된 거대한 인터페이스를 만들게 되면, 이를 상속받은 구체 클래스 역시 변경해야 할 이유가 두 가지 이상이 됨. 즉, ISP 위반은 필연적으로 SRP 위반으로 이어짐.
인터페이스는 얼마나 작아야 할까?
- "작은 인터페이스"라는 말을 무조건 '딱 한 가지 메서드만 있어야 한다’는 뜻으로 오해해서는 안 됨. 응집력 관점에서 하나의 클래스에 하나 이상의 메서드가 적절하게 속해 있을 수 있음.
- 예외 사례 (컨텍스트 관리자): 컨텍스트 관리자를 추상화한 클래스는
__enter__와__exit__두 가지 메서드를 반드시 함께 제공해야 함. 이 둘을 다른 클래스로 분리하면 유효한 컨텍스트 관리자가 될 수 없으며, 전혀 쓸모없는 코드가 됨.
- 예외 사례 (컨텍스트 관리자): 컨텍스트 관리자를 추상화한 클래스는
- 즉, 서로 완전히 관련된 로직이라면 여러 개의 메서드라도 하나의 인터페이스로 디자인하는 것이 좋음.
댓글