원글: Why extends is evil?

구현체 대신에 인터페이스를 사용함으로써 코드를 개선하자.

extends키워드는 될 수 있으면 지양해야한다. Gang of Four Design Patterns이란 책에서는 상속(extends)을 인터페이스 구현(implements)으로 바꾸는 것에 대해 설명하고 있다.

좋은 설계자는 코드의 대부분을 Concrete 클래스 대신에 인터페이스로 작성한다. 이 글은 설계자들이 이런 요상한 습관을 가지고 있는지 설명하고, 몇 가지 인터페이스 기반 프로그래밍의 기본을 소개한다.

인터페이스 vs. 클래스

나는 James Gosling(자바의 창시자 a.k.a. 자바의 아버지)이 연사로 있는 Java user group meeting에 참석한 적이 있다. Q&A 세션에서 누군가 그에게 질문했다:

Q: "다시 자바를 만든다면, 무엇을 바꾸고 싶으신가요?" A: "클래스를 제거하고 싶습니다."

웃음소리가 잦아든 후, 진짜 문제는 클래스 그 자체가 아니라 상속(extends를 통해서)이라고 설명했다. 인터페이스를 구현하는 게 더 바람직하다.(implements를 통해서) 클래스 상속은 피할 수 있으면 피해야한다.

유연성을 잃게된다.

왜 상속을 피해야하는가?

명시적으로 concrete 클래스를 쓰는 것은 해당 클래스에 갇히는 걸 의미하며, 변경하기 매우 어렵게 만든다.

애자일 개발 방법론의 핵심은 설계와 개발을 병렬로 수행하는 것이다. 프로그램이 구체화 되기 전에 프로그래밍을 시작할 수 있다. 이 기법은 프로그래밍을 시작하기 전에 설계가 완성돼야한다는 전통적인 기법과 상충하지만, 많은 프로젝트들이 이런 애자일 개발 방법론을 적용해서 더 빠르고 더 고품질의 코드를 작성할 수 있다는 걸 증명했다. 그러나 설계와 개발을 병렬로 수행하는 것의 핵심은 유연성이란 개념이다. 새로운 요구사항을 최대한 효율적으로 기존 코드에 통합할 수 있도록 코드를 작성해야한다.

필요할지도 모르는 걸 구현하는 것보다 확실히 필요한 기능만 구현하는 방법으로 변화를 수용해야한다. 이런 유연성이 없다면 설계와 개발을 병렬로 수행할 수 없다.

인터페이스로 프로그래밍하는 것은 유연한 구조의 핵심이다. 그 이유가 궁금하다면 아래 코드를 봐보자:

f() {   
		LinkedList list = new LinkedList();
	  //...
	  g(list);
}

g(LinkedList list) {
		list.add( ... );
	  g2(list);
}

이후에 빠른 검색을 위한 요구사항이 나왔다고 가정해보자. LinkedList는 이런 요구사항에 적합하지 않다. (역자 주: 인덱스가 없기 때문에 최악의 상황에 n번의 반복문을 수행해야하기 때문에) HashSet이야말로 이 요구사항에 적합하다. (역자 주: value를 해싱해서 보관하기 때문에 1번만에 바로 원하는 결과를 찾을 수 있기 때문에) 위 코드에서 f()는 물론 g()(LinkedList를 인자로 받는)도 수정해야한다.

위 코드를 인터페이스를 사용해서 재작성 하면 아래와 같다:

f() {   
		Collection c = new LinkedList();
	  //...
	  g(c);
}

g(Collection c) {
	  c.add( ... );
	  g2(c);
}

인터페이스를 쓴다면 linked list를 hash table로 바꾸는 것은 new LinkedList() 를 new HashSet()으로 바꾸는 것만으로 손쉽게 끝낼 수 있다.

이제 다른 코드를 보자: