역자 주: 이 글은 두 차례 갱신되었습니다. 인터페이스를 구현한 명시적인 클래스(Implementation)-예를 들어 java 환경에서라면 .class 파일이되겠죠-와 명시적으로 만들지 않아도 이미 전제가 되는 암묵적(implicit) 구현체 사이의 분류는 상당히 현학적으로 들릴 수 있습니다. 왜 이런 쓸데없는 고민을 하느냐고 물으실 분도 있을 것 같습니다. 짐작컨데 테스트를 중시하는 TDD 기반으로 개발을 하다보면, 다수의 인터페이스를 구현하는 클래스를 만들게 되고, 그러다보면 부딪히게 되는 고민일 것 같습니다.
Java 와 C# 모두는 순수한 인터페이스 형식을 고수한다. 만일 Mailable 이라는 인터페이를 선언하면 자바에서 class Customer implements Mailable 라는 문장으로 이를 구현할 수 있다. 하나의 클래스는 몇 개의 인터페이스라도 구현할 수 있다.
이러한 방식의 인터페이스 구현은 클래스를 정의할 때 생겨나는 암묵적인 인터페이스(implicit interfaces)를 고려하지 않는다. Customer 클래스에 정의된 모든 public members 는 Customer 의 암묵적 인터페이스가 된다. (내가 본 모은 객체지향언어에서는 이러한 암묵적인 인터페이스를 갖고 있다.)
Java 나 C# 은 암묵적 인터페이스를 구현할 방법을 제공하지 않는다. 즉, class ValuedCustomer implements Customer 와 같은 것을 허용하지 않는다. 암묵적 인터페이스를 구현한다는 것은 무엇을 의미할까? ValuedCustomer 클래스는 Customer 에 선언된 모든 public 메소드를 구현하지만, 반드시 이들 메소드의 구현체를 갖지는 않겠다는 것이다. 즉, public 메소드의 몸체 부분이나 public 이 아닌 메소드나 데이터를 구현하지 않을 수 있다는 것을 의미한다. 다시 말하면, 인터페이스를 상속하나 구현을 상속하지는 않겟다는 것을 의미한다.
어떤 경우에 이러한 기능이 요구될까? 현재와 같은 Collection 프레임워크가 추가되지 이전의Java 초기의 기억을 떠올려보겠다. 우리는 Vector 클래스를 자체적인 구현 클래스로 대치하고 싶었지만, Vector 는 클래스이기 때문에 상속하는 것만이 가능했다. 많은 사람들이 종종 라이브러리가 인터페이스를 제공하지 않아서, 불필요한 기능을 떠안고 상속을 해본 경험이 있을 것이다.
특히 최근에 테스팅 과정에서 이런 일이 비일비재하다. 어떤 클래스의 스텁(stub)을 만들려고 할 때, 인터페이스가 없는 클래스라면 까다로와지거나 아예 불가능한 경우가 생긴다. 테스트 전용으로 인터페이스를 정의해야 하는 일이 발생한다.
인터페이스와 구현 쌍(nterface Implementation Pair) 를 기본 접근 방식으로 활용하지 않는 상황이라면 이런 일은 상당히 꺼려질 것이다. 암묵적인 인터페이스 구현 방식이 보다 간명하게 적용할 수 있을 것이다.
그런데, 프로그래밍 언어가 왜 이들을 지원하지 않을까? 나는 언어 설계자가 아니기에 그 이유를 알 수 없다. Anders Heljsberg(Turbo Pascal, Delphi, C#, 닷넷 프레임워크 등의 설계자)에게 이를 물어볼 기회가 있었고, 명시적으로 멤버를 virtual 로 선언한 경우에 한해서 오버라이딩을 하는 것을 선호한다는 정도로밖에는 답변을 듣지 못했다. 그러나, 이는 주로 상위 클래스의 정의 내용에 변경이 가해지는 하위 클래스의 정의에 관한 것이었고, 상속을 다룬 포괄적인 이야기에 일부였던 관계었다. 게다가 저녁 시간의 짧은 대화였기에 충분한 의미를 부여하기도 애매하다.
이하는 새로 갱신된 내용입니다.
이글을 쓰고 난 후 나의 오랜 동료
Mike Rettig 가 이러한 방식 즉, 하나의 클래스가 암묵적으로 다수의 인터페이스를 구현하는 것에 대해 한가지 문제점을 지적했다. 자바의 예를 들면, Customer 라는 클래스가 있다고 할 때, 이는 암묵적으로 public, protected, package 그리고 private 이라고 하는 네 개의 인터페이스를 갖는다. Customer 객체와 협업하는 객체들은 이 중에 하나의 인터페이스를 쓰게 된다. Customer 객체의 다른 인스턴스는 private feature(프로퍼티나 오퍼레이션)를 쓸 수도 있다. 만일 암묵적인 인터페이스를 구현해야 한다고 가정하면, 이들 네 가지 모두를 구현해거나 그에 상응하는 무언가를 해야 한다. 나는 그것이 얼마나 어려운 작업인지 알 수도 없고, 암묵적으로는public 인터페이스만으로도 충분하다는 사례는 도처에서 발견할 수 있다.
Ian Griffiths 는 클래스와 인터페이스를 섞는 과정에서 발생할 수 있는 문제를 지적했다. Microsoft 의 COM 기술은 실제로는 이들 둘은 명확하게 구분한다: "COM 기반의 객체를 사용하려면 인터페이스를 활용해야 한다. 그래야 항상 고유의 구현 클래스를 보장할 수 있다." VB6 에서는 COM 인터페이스가 개발자가 인식하지 못하는 가운데서 생성되기 때문에 이에 대해 자유로울 수 있다.
동적 타입 기반의 언어(스크립트)에서 이러한 문제가 없다. 다른 클래스의 인터페이스를 구현하고 싶으면, 단지 동일한 메소드를 구현하고, 객체를 생성해서 쓰면 된다. 자바에서도 동적인 프록시(dynamic proxies)를 써서 이를 구현하는 것이 보편화 되어 있지만, 암묵적 인터페이스 구현을 지원하는 것이 보다 효과적인 프로그래밍을 짜게 할 것이다.
이러한 것이 왜 문제가 되는가? 나는 주로 이 문제가 테스트에 관련되어 있다고 생각한다. 만일 새로운 구현 클래스를 사용할 수 없는 상황이라면
테스트 전용 객체(Test Double)를 추가하는데 어려움을 겪을 것이고, 부모클래스가 실제 데이터베이스와의 연결을 필요로 하는 상황이라면 이를 상속한 클래스가
테스트 전용 객체 역할을 구현하는데 쉽지 않을 것이다. 이 문제는 단순히 테스트에 국한된 것일 수 있다 - Robert Conley 의 말에 따르면, 그는 테스트에서만 사용할 것들에 대해서는 VB6 (앞서 언급한 측면) 를 많이 사용한다고 한다.
Trackback Address :: http://younghoe.info/trackback/24