숏컷 코드
너무 큰 계약
-> 안 쓰는 메서드도 구현
얇은 계약
-> 필요한 기능만 구현문법과 예시
ISP는 "큰 인터페이스를 나누는 것"보다 "불필요한 의존을 줄이는 것"이다
IGameEntity에 이동, 공격, 저장, 상호작용, 인벤토리, UI 갱신까지 다 넣으면 대부분의 구현이 안 쓰는 메서드를 억지로 가지게 됩니다.
이 구조는 인터페이스가 많아서가 아니라, 호출부가 필요 이상으로 많은 능력을 기대해서 생기는 문제입니다.
게임에서는 상호작용 축별로 나누는 편이 자연스럽다
IDamageable, IInteractable, IMovable, ISaveTarget처럼 호출 의도가 다른 계약을 나누면, 시스템도 필요한 축만 의존하게 됩니다.
이 방식은 특히 Unity의 컴포넌트 구조와 잘 맞습니다.
나쁜 예
IGameEntity
- Move()
- TakeDamage()
- Save()
- OpenShop()
더 나은 예
IMovable
IDamageable
ISaveTarget
IShopProviderISP는 호출부를 더 단순하게 만든다
공격 시스템은 IDamageable만 알면 되고, 저장 시스템은 ISaveTarget만 알면 됩니다. 계약이 얇아질수록 테스트 더블도 단순해지고, 교체 가능한 구현 범위도 선명해집니다.
가장 좋은 분리 기준은 "누가 이 메서드를 실제로 호출하는가"다
인터페이스를 기능 목록으로 나누기보다 호출 주체 기준으로 나누면 더 오래 갑니다. 전투 시스템이 부르는 계약, 저장 시스템이 부르는 계약, 상호작용 시스템이 부르는 계약이 다르면 인터페이스도 그 축을 따라가는 편이 자연스럽습니다.
ISP를 볼 때 핵심
| 상황 | 적합한 선택 |
|---|---|
| 구현 클래스가 안 쓰는 메서드를 자주 비워 둘 때 | 인터페이스 분리 |
| 시스템마다 필요한 기능 축이 다를 때 | 얇은 계약으로 분리 |
| 호출부가 특정 기능 하나만 필요할 때 | 그 기능만 담은 인터페이스 |
| 실제로 항상 함께 움직이는 계약일 때 | 묶어서 유지 가능 |
특이 케이스와 주의할 점
흔한 실패는 작은 인터페이스를 무한히 늘려서 오히려 의미가 흐려지는 것입니다. 얇아야 하지만 의미도 있어야 합니다. 호출 의도 기준으로 잘라야지 메서드 한 개마다 무조건 분리할 필요는 없습니다.
실패 예시
- ICanOpen
- ICanOpenShop
- ICanOpenQuest
- ICanOpenInventory
결과
- 계약 이름은 늘지만 구조 의미는 더 흐려짐참고 링크
1 sources