객체 지향 관점에서의 interface
언젠가 동생이 내게 이런 질문을 한 적이 있다. "형은 어떨 때 type을 쓰고 어떨 때 interface를 써?" 그리고 나는 대부분의 경우 type을 쓴다고 대답했다. 이유는 간단한데, 선언 병합 외에 interface가 더 나은 점을 찾을 수 없었기 때문이었다. 확실히 '기능'만 놓고 본다면 굳이 interface를 쓸 이유가 없어보이기는 한다. 하지만 동생이 다시 한 번 질문을 해온다면 지금의 나는 이렇게 대답할 것 같다.
인터페이스라는 표현을 가장 자주 접하는 건 아마도 API지 않을까 싶다. 특히 나는 웹 개발자라 그런가 REST API가 제일 먼저 떠오르는데, 여기서는 "독립되고 관계가 없는 시스템이 접촉하거나 통신이 일어나는 부분"을 인터페이스라고 부른다. 웹서버와 백엔드 서버는 독립되고 관계가 없는 시스템이지만 정해진 규약을 따라 서로 요청을 주고받으며 데이터를 주고받는다. 이때 중요한 건, 웹서버는 백엔드 서버의 내부 구현을 전혀 몰라도 된다는 점이다. 오직 정해진 인터페이스만 따르면 원하는 정보를 얻을 수 있다.
타입스크립트의 interface도 마찬가지다. 인터페이스는 객체가 외부와 소통하는 방식을 정의하며, 내부 구현과 분리된 "계약"의 역할을 한다. 객체가 어떤 기능을 제공하는지 명확하게 드러내는 점에서, 단순히 타입을 정의하는 type보다 개념적으로 더 적절한 선택이 될 수 있다.
조금 더 들어가보자. 일반적으로 객체를 떠올릴 때는 그 객체가 가진 상태와 동작을 함께 생각하게 된다. 객체의 상태는 속성(properties)으로 표현되고, 동작은 메서드(methods)로 표현된다. 그런데 객체 지향에서는 한 가지가 더 포함되게 된다. 바로 메시지(message)다. 객체 지향에서 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 메시지를 전송하는 것 뿐이고, 메시지를 수신한 객체는 스스로의 결정에 따라 메시지를 처리할 방법을 자율적으로 결정한다.
캡슐화를 단순히 '외부 환경이 객체 내의 상태에 접근하는 것을 막고, 정해진 방식으로만 접근할 수 있게 한다'고 생각할 수도 있다. 하지만 캡슐화는 그 이상의 의미를 가진다. 객체 지향을 공부하면서 좌우명처럼 늘 반추하는 명언이 있는데 ─ 누가 한 말인지 어디서 읽었는지는 기억이 나지 않지만 ─ "캐셔가 지갑을 가져가는 것이 아니라, 손님이 스스로 돈을 지불해야 한다" 라는 것이다. 이처럼 캡슐화는 외부에서의 불필요한 간섭을 최소화하는 것만이 아니라, 객체가 스스로의 동작 방식을 결정할 수 있게 하는 중요한 개념이다.
[ 추상 클래스와 인터페이스 ] 포스트에서 나는 "인터페이스는 상태를 포함하지 않고, 오직 메서드의 속성과 구조만을 정의"한다고 서술했다. 이제 나는 이 문장이 틀렸다는 것을 안다. 인터페이스는 상태만 포함하지 않는 게 아니라, 메서드도 포함하지 않는다. 인터페이스는 오직 메시지만 포함한다.
손님은 본인에게 돈이 얼마나 있는지, 어떤 지갑을 쓰는지, 돈을 지갑에 넣는지 가방에 넣는지 주머니에 넣는지를 캐셔에게 알려줄 이유가 없다. 손님은 그저 '손님.계산_부탁드립니다(총_금액)' 메시지에 응하거나 응하지 않을 뿐이다. 또한 캐셔는 ─ 객체 지향적인 이유로 ─ 손님에게 돈이 얼마나 있는지 어떤 지갑을 쓰는지 알 필요가 없다. 손님 인터페이스가 노출하는 계산_부탁드립니다 메시지를 사용하거나 사용하지 않을 뿐.
이 관점에서 보면, interface만 선언 병합을 지원하는 이유를 자연스럽게 이해할 수 있다. 인터페이스는 객체 간의 메시지를 정의하는 역할을 하며, 메시지를 처리하는 방법은 객체 내부에서 자유롭게 결정된다. 따라서 동일한 메시지를 여러 부분에서 제공하려는 경우, 인터페이스의 선언 병합 기능은 매우 유용하다. 객체 간 메시지의 명세는 점진적으로 확장되어야 하기 때문에, 타입스크립트는 interface를 통해 선언 병합을 지원하는 것이다.
이 포스트 내내 나는 interface의 대상을 클래스가 아니라 '객체'라고 표현했다. 이는 어느 정도 자기반성적인 의미를 내포하고 있다. 지금까지 객체 리터럴의 타입을 작성할 때, 상태와 메서드를 모두 명시해버리기 일쑤였기 때문이다. private 한정자로 제어되는 프로퍼티에 대한 접근이 불가능한 것처럼, 인터페이스에 노출되지 않은 프로퍼티에 접근하려고 할 때도 타입 오류가 발생한다. 비록 실제로 그 값이 객체에 존재한다고 하더라도, 런타임에서 아무런 문제가 없을 때조차 타입 시스템은 이를 허용하지 않는다.
캡슐화의 관점에서 이를 생각해보면 interface의 목적성은 더욱 뚜렷해진다. 속성과 메서드는 감추고, 메시지만 드러내라.
+ "Semantics are important." 이건 레딧에서 들은 조언인데, 그 이후로 꾸준히 명심하고 있다. 물론 type으로도 속성과 메서드는 감추고 메시지만 드러낼 수 있다. 하지만 의미론은 중요하다. interface라는 아주 객체지향적인 의미를 가진 단어를 사용함으로써, 우리는 그 목적성과 의도를 명확하게 드러낼 수 있다. interface는 단순히 타입을 정의하는 것 이상의 의미를 지니는 것이다.
블로그의 정보
Ayden's journal
Beard Weard Ayden