인터페이스와 상속, 중복 코드를 없애는 원리

인터페이스와 상속은 객체지향에서 중복을 제거하는 두 축입니다. 둘 다 “같은 코드를 여러 벌 두지 않는다”는 목표를 향하지만, 접근하는 방향이 다릅니다. 인터페이스는 무엇을 할지 약속하고, 상속은 어떻게 할지를 물려줍니다. 이 한 줄의 차이를 이해하면 둘을 헷갈릴 일이 없습니다.

이 글은 두 개념이 왜 중복을 줄이는지를 원리 중심으로 풀어봅니다. 문법보다 “왜 이렇게 설계하면 코드가 안전해지는가”에 초점을 맞췄습니다.

인터페이스 — “이렇게 할 수 있어야 한다”는 약속

인터페이스는 구현을 강제하지 않고 행위의 약속만 정의합니다. “너는 결제하고 취소할 수 있어야 해. 방법은 네 마음대로”라고 선언하는 것이죠.

public interface PaymentMethod {
    PaymentResult pay(Money amount);
    void cancel(String transactionId);
}

이 약속만 지키면 카드든 간편결제든, 내일 새로 생길 암호화폐 결제든 상관없습니다. 호출하는 쪽은 pay()만 부르면 되고 내부 동작은 알 필요가 없습니다.

원리적으로 중요한 지점은 여기입니다. 결제 수단이 하나 추가될 때마다 기존 코드를 뜯어고쳐야 한다면, 추가할 때마다 버그 확률이 올라갑니다. 인터페이스는 그 사슬을 끊습니다. 추가는 추가일 뿐, 기존 코드는 건드리지 않으니까요. 변경에 닫혀 있고 확장에 열려 있는, 이른바 개방-폐쇄 원칙이 작동하는 방식입니다.

상속 — 공통 속살을 한 번만 쓰고 물려준다

상속은 더 단순합니다. 부모가 가진 것을 자식이 그대로 받습니다.

회원이든 관리자든 게스트든 로그인하면 기록을 남긴다고 해봅시다. 이 로그 코드를 세 번 복붙할 수도, 부모 클래스에 한 번 써두고 셋이 물려받게 할 수도 있습니다. 로그 양식이 바뀌는 날, 전자는 세 군데를 고쳐야 하고 후자는 한 군데만 고치면 됩니다. 같은 로직이 한 곳에 모여 있는가, 흩어져 있는가의 차이입니다.

왜 중복이 그렇게 위험한가

코드가 두 줄에서 네 줄로 늘어나는 게 무서운 게 아닙니다. 진짜 위험은 고칠 때 드러납니다.

같은 로직이 흩어져 있으면한 곳에 모여 있으면
수정할 때마다 다 찾아다녀야 함한 군데만 고치면 끝
하나 빠뜨리면 그게 곧 장애빠뜨릴 곳 자체가 없음
이해도 낮으면 누락 확정누가 와도 안전

처음 프로젝트를 만진 사람은 결제 로직이 네 군데 흩어져 있다는 걸 알 리가 없습니다. 하나 고치고 끝났다고 믿겠죠. 잘못이 아니라 구조가 그렇게 만든 겁니다. 실제로 결제 수수료 정책이 바뀐 날, 네 군데에 복사된 로직 중 하나를 놓쳐 다음 날 새벽 장애로 이어진 경험이 있습니다. 문제는 부주의가 아니라 같은 코드가 네 벌 존재한다는 사실 자체였습니다.

다만, 무조건 묶는 것이 답은 아니다

여기서 균형이 필요합니다. 공통점이 거의 없는데 억지로 부모 클래스로 묶으면, 자식들이 쓰지도 않는 메서드를 물려받고 누군가 “이 함수 왜 여기 있지?” 하며 머리를 싸매게 됩니다. 과한 추상화는 중복만큼이나 골치 아픕니다.

그래서 실무에서는 두 가지 경험칙을 씁니다. 첫째, 같은 코드가 세 번 반복되면 그때 묶습니다. 두 번까지는 우연일 수 있습니다. 둘째, 상속보다 인터페이스나 조합(composition)을 먼저 떠올립니다. 상속은 부모와 자식을 단단히 묶어버리므로, 정말 “~는 ~다” 관계가 분명할 때만 쓰는 것이 안전합니다.

정리하면 둘의 쓰임은 이렇게 갈립니다. 약속만 정하고 구현은 자유롭게 두고 싶으면 인터페이스, 공통 코드까지 물려주고 싶으면 추상 클래스나 상속입니다. 실무에선 둘을 함께 쓰는 경우가 더 많습니다. 새 기능 요청이 오면 비슷한 게 이미 있는지 먼저 찾고, 공통 행위는 인터페이스로 약속하고, 반복되는 속살만 부모에 모으면 대개 “수정”이 아니라 “추가”로 일이 끝납니다. 안 건드리니 안 깨집니다.

참고: Refactoring Guru — 디자인 패턴

“인터페이스와 상속, 중복 코드를 없애는 원리”에 대한 3개의 생각

댓글 남기기