Java에서 "인터페이스에 프로그래밍"하는 것이 항상 의미가 있습니까?


9

인터페이스에서 구현하는 클래스를 인스턴스화하는 방법에 대한 질문에 대한 토론을 보았습니다 . 제 경우에는의 인스턴스를 사용하는 매우 작은 프로그램을 Java로 작성하고 있으며 TreeMap모든 사람들의 의견에 따라 다음과 같이 인스턴스화해야합니다.

Map<X> map = new TreeMap<X>();

내 프로그램 map.pollFirstEntry()에서 Map인터페이스 (및 인터페이스 에있는 다른 두 사람) 에서 선언되지 않은 함수를 호출하고 있습니다 Map. 나는 TreeMap<X>이 방법을 호출 하는 모든 곳 으로 캐스팅 하여이 작업을 수행했습니다 .

someEntry = ((TreeMap<X>) map).pollFirstEntry();

나는 큰 프로그램에 대해 위에서 설명한 초기화 지침의 장점을 이해하지만이 객체가 다른 방법으로 전달되지 않는 매우 작은 프로그램의 경우 필요하지 않다고 생각합니다. 여전히이 샘플 코드를 작업 응용 프로그램의 일부로 작성하고 있으며 코드가 나쁘거나 어수선하게 보이고 싶지 않습니다. 가장 우아한 해결책은 무엇입니까?

편집 : 특정 기능을 적용하는 대신 광범위한 우수한 코딩 방법에 더 관심이 있음을 지적하고 싶습니다 TreeMap. 일부 답변이 이미 지적 했으므로 (그리고 첫 번째 답변으로 답변 한 것으로 표시됨) 기능을 잃지 않고 가능한 더 높은 추상화 수준을 사용해야합니다.


1
기능이 필요할 때 TreeMap을 사용하십시오. 아마도 특정 이유로 디자인을 선택했을 수도 있으므로 구현의 일부 여야합니다.

@JordanReiter 동일한 콘텐츠로 새로운 질문을 추가해야합니까, 아니면 내부 교차 게시 메커니즘이 있습니까?
jimijazz


2
"대규모 프로그램에 대해 위에서 설명한 초기화 지침의 장점을 이해합니다."프로그램의 규모에 관계없이 모든 곳에서 캐스트를하는 것이 유리하지 않습니다.
Ben Aaronson

답변:


23

"인터페이스 프로그래밍"이 "가장 추상화 된 버전 사용"을 의미하지는 않습니다. 이 경우 모두가 사용 Object합니다.

그 의미는 기능을 잃지 않고 가능한 가장 낮은 추상화에 대해 프로그램을 정의해야한다는 것 입니다. 를 요구할 경우 TreeMap를 사용하여 계약을 정의해야합니다 TreeMap.


2
TreeMap은 인터페이스가 아니며 구현 클래스입니다. 인터페이스 Map, SortedMap 및 NavigableMap을 구현합니다. 설명 된 방법은 NavigableMap 인터페이스의 일부입니다 . TreeMap을 사용하면 구현이 구현이 아닌 인터페이스에 대한 전체 코딩 지점 인 ConcurrentSkipListMap (예 :)으로 전환되지 않습니다.

3
@ MichaelT : 나는이 특정 시나리오에서 필요한 정확한 추상화를 보지 않았으므로 TreeMap예제로 사용 했습니다. "인터페이스로의 프로그램"은 문자 그대로 인터페이스 또는 추상 클래스로 간주되어서는 안됩니다. 구현은 인터페이스로 간주 될 수도 있습니다.
Jeroen Vannevel

1
구현 클래스의 퍼블릭 인터페이스 / 메소드가 기술적으로 '인터페이스'이지만 LSP 의 개념을 깨뜨리고 다른 서브 클래스의 대체를 방지 public interface하므로 '공개의 퍼블릭 메소드'보다는 프로그래밍하려는 이유 .

@JeroenVannevel 인터페이스 가 실제로 클래스로 표현 되면 인터페이스 프로그래밍가능 하다는 데 동의합니다 . 그러나, 나는 사용하여 혜택을 확인하지 않는 이상 것 또는TreeMapSortedMapNavigableMap
toniedzwiedz

16

여전히 인터페이스를 사용하려면 사용할 수 있습니다

NavigableMap <X, Y> map = new TreeMap<X, Y>();

항상 인터페이스를 사용할 필요는 없지만 구현을 대체 할 수있는 더 일반적인 견해를 원할 때가 있습니다 (어쩌면 테스트를 위해). 객체에 대한 모든 참조가 다음과 같이 추상화되면 쉽습니다. 인터페이스 유형.


3

구현이 아닌 인터페이스에 대한 코딩 배후의 요점은 프로그램을 제한하는 구현 세부 사항의 유출을 피하는 것입니다.

원래 코드 버전에서이를 사용하여 HashMap노출 한 상황을 고려하십시오 .

private HashMap foo = new HashMap();
public HashMap getFoo() { return foo; }  // This is bad, don't do this.

이는에 대한 변경 getFoo()이 API 변경을 어 기고이를 사용하는 사람들을 불행하게 만들 것임을 의미합니다 . 당신이 보장하는 모든 것이 그것이 foo지도라면, 대신 그것을 돌려 주어야합니다.

private Map foo = new HashMap();
public Map getFoo() { return foo; }

이를 통해 코드 내에서 작동하는 방식을 유연하게 변경할 수 있습니다. foo실제로는 특정 순서로 물건을 돌려주는 맵이어야 함을 알고 있습니다.

private NavigableMap foo = new TreeMap();
public Map getFoo() { return foo; }
private void doBar() { ... foo.lastEntry(); ... }

그리고 그것은 나머지 코드에서 아무것도 깨지지 않습니다.

나중에 클래스가 제공하는 계약을 중단하지 않고 강화할 수 있습니다.

private NavigableMap foo = new TreeMap();
public NavigableMap getFoo() { return foo; }
private void doBar() { ... foo.lastEntry(); ... }

이것은 Liskov 대체 원칙을 탐구합니다.

대체 가능성은 객체 지향 프로그래밍의 원칙입니다. 컴퓨터 프로그램에서, S가 T의 서브 타입이면, 유형 T의 객체는 바람직한 임의의 변경없이 유형 S의 객체 (즉, 유형 S의 객체가 유형 T의 객체를 대체 할 수 있음)로 대체 될 수 있다고 기술한다. 해당 프로그램의 속성 (정확성, 수행 된 작업 등)

NavigableMap은 Map의 하위 유형이므로이 대체는 프로그램을 변경하지 않고 수행 할 수 있습니다.

구현 유형을 공개하면 변경이 필요할 때 프로그램이 내부적으로 작동하는 방식을 변경하기가 어렵습니다. 이것은 고통스러운 과정이며 나중에 코더에 더 많은 고통을 가하는 데 도움이되는 추악한 해결 방법을 만듭니다. 내가 걱정하는 svn blame에서 당신의 이름을보십시오).

여전히 구현 유형의 유출을 피하고 싶을 것 입니다. 예를 들어, 일부 성능 특성으로 인해 대신 ConcurrentSkipListMap을 구현할 수도 있고 import 문이나 다른 java.util.concurrent.ConcurrentSkipListMap것보다는 오히려 좋아할 수도 있습니다 java.util.TreeMap.


1

다른 답변에 동의하면 실제로 필요한 것이 가장 일반적인 클래스 (또는 인터페이스)를 사용해야한다는 것입니다.이 경우 TreeMap (또는 누군가 제안한 NavigableMap). 그러나 나는 이것이 어느 곳에서나 캐스팅하는 것보다 가장 확실하다고 덧붙이고 싶습니다. 어쨌든 훨씬 더 큰 냄새가납니다. 몇 가지 이유로 /programming/4167304/why-should-casting-be-avoided 를 참조 하십시오 .


1

그것은 객체가 어떻게 사용되어야 하는지를 알리기위한 것 입니다 . 예를 들어, 메소드 가 예측 가능한 반복 순서를 가진 객체를 기대하는 경우 :Map

private Map<String, String> processOrderedMap(LinkedHashMap<String, String> input) {
    // ...
}

그런 다음 위 메소드의 호출자에게 예측 가능한 반복 순서를Map 가진 오브젝트 도 리턴하도록 지시 해야하는 경우 , 어떤 이유로 인해 그러한 기대가 있기 때문입니다.

private LinkedHashMap<String,String> processOrderedMap(LinkedHashMap<String,String> input) {
    // ...
}

물론, 호출자는 여전히 반환 객체를 그대로 취급 할 수 Map있지만 메소드의 범위를 벗어납니다.

private Map<String, String> output = processOrderedMap(input);

물러서

인터페이스에 코딩의 일반적인 조언이다 (일반적으로) 적용하기 때문에 일반적으로 는 개체가 일명 수행 할 수 있어야합니다 것을 보장 제공하는 인터페이스의 계약을 . 많은 초보자는 처음부터 시작해야 HashMap<K, V> map = new HashMap<>()하며을 (를) 선언해야합니다 Map. 왜냐하면 HashMapa Map는해야 할 것 이상을 제공하지 않기 때문 입니다. 이것으로부터, 그들은 그들의 메소드가 왜 Map대신에 받아 들여야 하는지를 이해할 수있을 것입니다. HashMap이것은 OOP의 상속 기능을 깨닫게합니다.

이 주제와 관련된 모든 사람이 좋아하는 원칙 의 Wikipedia 항목에서 한 줄만 인용하십시오 .

계층 구조에서 유형의 의미 론적 상호 운용성을 보장하기 때문에 단순히 구문 적 관계가 아닌 의미 론적입니다 ...

다시 말해서 Map선언을 사용하는 것은 '구문 적으로'의미가 있기 때문이 아니라 오히려 객체에 대한 호출은 단지 그것이 유형이라는 것을 신경 써야합니다 Map.

클리너 코드

특히 단위 테스트와 관련하여 더 깨끗한 코드를 작성할 수도 있습니다. 만들기 HashMap하나의 읽기 전용 테스트 항목과 것은 내가 쉽게로 그것을 대체 할 때, 더 라인 (이중 중괄호 초기화 사용 제외)보다 소요됩니다 Collections.singletonMap().

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.