의미있는 클래스 이름으로 Java 콜렉션을 마스킹하는 좋은 방법 또는 나쁜 방법?


46

최근에 저는 인간 친화적 인 클래스 이름으로 Java 콜렉션을 "마스킹"하는 습관을 겪었습니다. 몇 가지 간단한 예 :

// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}

또는:

// If you saw a ArrayList<ArrayList<?>> being passed around in the code, would you
// run away screaming, or would you actually understand what it is and what
// it represents?
public class Changelist extends ArrayList<ArrayList<SomePOJO>> {
}

동료가 나에게 이것이 나쁜 습관이고 지연 / 대기 시간뿐만 아니라 OO 반 패턴이라는 것을 지적했습니다. 매우 작은 수준의 성능 오버 헤드 가 발생한다는 것을 이해할 수는 있지만 그것이 중요하다는 것을 상상할 수는 없습니다. 그래서 나는 묻습니다 : 이것이 좋은지 나쁜지, 왜 그런가요?


11
이것보다 훨씬 간단합니다. Java 기본 JDK Collection의 구현을 확장하고 있다고 생각하기 때문에 나쁜 습관입니다. Java에서는 하나의 클래스 만 확장 할 수 있으므로 확장 기능이있을 때 더 생각하고 디자인해야합니다. Java에서는 확장을 조금만 사용하십시오.
정보 :

ChangeListList 는 인터페이스 extends이기 때문에 컴파일이 중단됩니다 . @random 당신이 상상하는 것은이 오류 때문에 요점을 놓치게됩니다implements
gnat

그것은 요점되지 @gnat, 나는 그가 연장되었다고 가정하고 implementationHashMap또는 TreeMap어떤 그가 오타가 있었다 가지고 있습니다.
정보 : A

4
이것은 나쁜 습관입니다. 나쁘다 나쁘다 이러지 마 누구나 Map <String, Widget>이 무엇인지 알고 있습니다. 그러나 WidgetCache? 이제 WidgetCache.java를 열어야합니다. WidgetCache는 단지 맵이라는 것을 기억해야합니다. 새 버전이 나올 때마다 WidgetCache에 새로운 것을 추가하지 않았다는 것을 확인해야합니다. 신 이시여, 절대 이러지 마십시오.
Miles Rout

"코드에서 ArrayList <ArrayList <? >>가 전달되는 것을 보았을 때 비명을 지르고 ...? 아니요, 중첩 된 일반 컬렉션에 상당히 익숙합니다. 그리고 당신도 그래야합니다.
Kevin Krumwiede

답변:


75

지연 / 지연? 나는 BS를 부른다. 이 연습에서 오버 헤드가 정확히 없어야합니다. ( 편집 : 실제로 HotSpot VM에서 수행하는 최적화를 방해 할 수 있다고 의견에서 지적되었습니다. VM 구현에 대해 충분히 알지 못하여이를 확인하거나 거부합니다. C ++에 대한 의견을 근거로했습니다. 가상 함수 구현)

코드 오버 헤드가 있습니다. 원하는 기본 클래스에서 매개 변수를 전달하여 모든 생성자를 작성해야합니다.

나는 또한 그 자체를 반 패턴으로 보지 않는다. 그러나 나는 그것을 놓친 기회로 본다. 이름 바꾸기를 위해 기본 클래스를 파생시키는 클래스를 만드는 대신 컬렉션 을 포함 하고 사례 별 개선 된 인터페이스를 제공하는 클래스를 만드는 방법은 무엇입니까? 위젯 캐시가 실제로지도의 전체 인터페이스를 제공해야합니까? 아니면 특수 인터페이스를 제공해야합니까?

또한 컬렉션의 경우 패턴은 구현이 아닌 인터페이스를 사용하는 일반적인 규칙과 함께 작동하지 않습니다. 즉 일반 컬렉션 코드에서는을 HashMap<String, Widget>만든 다음 유형의 변수에 지정합니다 Map<String, Widget>. 귀하의 WidgetCache캔은 연장되지는 Map<String, Widget>그 인터페이스이기 때문에. HashMap<String, Widget>해당 인터페이스를 구현하지 않으며 다른 표준 컬렉션도 수행하지 않기 때문에 기본 인터페이스를 확장하는 인터페이스 일 수 없습니다 . 그리고 그것을 확장하는 클래스로 만들 수는 있지만 HashMap<String, Widget>변수를 WidgetCacheor 로 선언해야 Map<String, Widget>하며 첫 번째 변수 는 다른 컬렉션을 대체 할 수있는 유연성을 잃어 버릴 수 있습니다 (ORM의 지연 로딩 컬렉션 일 수도 있음). 수업을받는 것.

이러한 반론 중 일부는 내가 제안한 전문 수업에도 적용됩니다.

이것들은 모두 고려해야 할 사항입니다. 올바른 선택 일 수도 있고 아닐 수도 있습니다. 두 경우 모두 동료의 제안 된 주장이 유효하지 않습니다. 그것이 반 패턴이라고 생각한다면, 그 이름을 지정해야합니다.


1
성능에 약간의 영향을 미칠 수는 있지만 끔찍한 것은 아닙니다 (일부 인라인 최적화를 놓치고 최악의 경우 여분의 vcall을 얻음-가장 안쪽 루프에서는 크지 않지만 그렇지 않을 수도 있음).
Voo

5
이 정답은 모든 사람에게 "성능 오버 헤드로 결정을 판단하지 마십시오"라고 말하며, 여기에서 유일한 논의는 "핫스팟이이를 어떻게 처리하고, 모든 경우의 99.9 %에서 관련이없는 성능 영향이 있습니까?" 첫 번째 문장보다 더 많은 것을 읽으려고 귀찮게 했습니까?
Doc Brown

16
+1 "대신 당신이 대신 수집 및 제공하는 경우 별, 개선 된 인터페이스를 포함하는 클래스를 생성에 대한 방법, 이름 변경을 위해 기본 클래스를 파생 클래스를 만드는?"
user11153

6
이 답변의 요점을 설명 실용 예 : 문서 에 대한 public class Properties extends Hashtable<Object,Object>인은 java.util해시 테이블, put 메소드 및 putAll 메소드 속성 상속은 등록 정보 객체에 적용 할 수 있기 때문에 "라고하면 (자), 호출 측에 키 항목을 삽입 할 수 있도록 그들의 사용은 권장하지 않습니다. 또는 값이 문자열이 아닙니다. " 구성이 훨씬 깨끗했을 것입니다.
Patricia Shanahan 2016 년

3
@DocBrown 아니요,이 답변은 성능에 전혀 영향을 미치지 않는다고 주장합니다. 만약 당신이 강한 진술을한다면 당신은 그것들을 더 잘지지 할 수 있습니다. 그렇지 않으면 사람들이 당신을 불러 낼 것입니다. 의견의 전체 요점 (답변)은 부정확 한 사실이나 가정을 지적하거나 유용한 메모를 추가하지만 사람들을 축하하지 않는 것은 투표 시스템의 목적입니다.
Voo

25

IBM에 따르면 이것은 실제로 안티 패턴입니다. 이러한 'typedef'클래스를 유사 유형이라고합니다.

이 기사에서는 나보다 훨씬 더 잘 설명하지만 링크가 다운되는 경우 요약하려고합니다.

  • WidgetCache처리 할 수없는 코드 는Map<String, Widget>
  • 이러한 의사 유형은 여러 패키지를 사용할 때 '바이러스 적'이며 비 호환성을 유발하는 반면 기본 유형 (어리석은 Map <...>)은 모든 패키지에서 모든 경우에 작동했습니다.
  • 의사 유형은 종종 구체적이며 기본 클래스는 일반 버전 만 구현하기 때문에 특정 인터페이스를 구현하지 않습니다.

이 기사에서는 의사 유형을 사용하지 않고 인생을 더 쉽게 만들기 위해 다음과 같은 트릭을 제안합니다.

public static <K,V> Map<K,V> newHashMap() {
    return new HashMap<K,V>(); 
}

Map<Socket, Future<String>> socketOwner = Util.newHashMap();

자동 유형 유추로 인해 작동합니다.

( 관련 스택 오버플로 질문 을 통해이 답변에 도달했습니다 )


13
newHashMap다이아몬드 운영자가 지금 해결할 필요가 없습니까?
svick 2016 년

물론 사실은 잊어 버렸습니다. 나는 보통 자바에서 일하지 않는다.
Roy T.

언어가 다른 유형의 인스턴스에 대한 참조를 보유하는 변수 유형의 유니버스를 허용하지만 유형 값을 유형 WidgetCache의 변수에 할당 WidgetCache하거나 Map<String,Widget>캐스트하지 않고 컴파일 타임 검사를 계속 지원할 수 는 있지만 정적 메소드가 원하는 유효성 검사를 수행 한 후 이를 WidgetCache참조하여 Map<String,Widget>유형으로 리턴 할 수 있는 수단이었습니다 WidgetCache. 이러한 기능은 ... 타입 지워 이후 (수 없을 것 제네릭, 특히 유용 할 수
supercat

... 유형은 어쨌든 컴파일러의 마음에만 존재할 것입니다). 많은 변경 가능한 유형의 경우, 두 가지 공통 유형의 참조 필드가 있습니다. 하나는 변경 될 수있는 인스턴스에 대한 유일한 참조를 보유하거나 다른 하나는 변경할 수없는 인스턴스에 대한 공유 가능한 참조를 보유하는 것입니다. 서로 다른 사용 패턴이 필요하기 때문에이 두 종류의 필드에 다른 이름을 지정할 수 있으면 도움이되지만 같은 유형의 객체 인스턴스에 대한 참조를 보유해야합니다.
supercat

5
이것은 왜 자바가 타입 앨리어스를 필요로하는지에 대한 훌륭한 주장이다.
GlenPeterson

13

성능 저하는 최대 vtable 조회로 제한되며 이미 발생했을 가능성이 높습니다. 그것이 반대하는 정당한 이유가 아닙니다.

대부분의 정적으로 유형이 지정된 프로그래밍 언어는 별칭 유형에 대한 특수 구문 (일반적으로이라고 함)을 갖기 때문에 상황이 일반적 typedef입니다. Java는 원래 매개 변수화 된 유형이 없기 때문에 복사하지 않았을 것입니다. Sebastian이 그의 대답에서 잘 설명했기 때문에 클래스 확장은 이상적이지 않지만 Java의 제한된 구문에 대한 합리적인 해결 방법이 될 수 있습니다.

Typedef에는 여러 가지 장점이 있습니다. 보다 적절한 수준의 추상화에서 더 나은 이름으로 프로그래머의 의도를보다 명확하게 표현합니다. 디버깅 또는 리팩토링 목적을 쉽게 검색 할 수 있습니다. 를 WidgetCache사용 하는 모든 곳을 찾는 것과 그 특정 용도를 찾는 것을 고려하십시오 Map. 예를 들어 나중에 LinkedHashMap또는 사용자 정의 컨테이너 가 필요한 경우 변경하기가 더 쉽습니다 .


생성자에도 약간의 오버 헤드가 있습니다.
Stephen C

11

이미 언급했듯이 상속보다 컴포지션을 사용하는 것이 좋습니다. 따라서 의도 한 사용 사례와 일치하는 이름으로 실제로 필요한 메소드 만 노출 할 수 있습니다. 수업의 사용자가 WidgetCache지도 인지 알아야 합니까? 그리고 그들이 원하는 것을 할 수 있습니까? 아니면 위젯의 캐시인지 알아야합니다.

내 코드베이스의 예제 클래스와 비슷한 문제에 대한 해결책이 있습니다.

public class Translations {

    private Map<Locale, Properties> translations = new HashMap<>();

    public void appendMessage(Locale locale, String code, String message) {
        /* code */
    }

    public void addMessages(Locale locale, Properties messages) {
        /* code */
    }

    public String getMessage(Locale locale, String code) {
        /* code */
    }

    public boolean localeExists(Locale locale) {
        /* code */
    }
}

내부적으로 "단지지도"인 것을 알 수 있지만 공개 인터페이스에는 표시되지 않습니다. 그리고 appendMessage(Locale locale, String code, String message)새 항목을 더 쉽고 의미있게 삽입 하는 것과 같은 "프로그래머 친화적"방법이 있습니다. 그리고 클래스 사용자는 확장하지 translations.clear()않기 때문에 예를 들어 할 수 없습니다 .TranslationsMap

선택적으로 필요한 메소드 중 일부를 내부적으로 사용 된 맵에 위임 할 수 있습니다.


6

나는 이것이 의미있는 추상화의 예라고 생각합니다. 좋은 추상화에는 몇 가지 특성이 있습니다.

  1. 코드를 소비하는 코드와 무관 한 구현 세부 사항을 숨 깁니다.

  2. 필요한만큼만 복잡합니다.

확장하면 부모의 전체 인터페이스를 노출하지만 대부분의 경우 그중 많은 부분이 숨겨져있을 수 있으므로 Sebastian Redl이 제안한 것을 상속보다 구성을 선호 하고 부모 인스턴스를 다음과 같이 추가 하고 싶습니다. 맞춤 클래스의 비공개 멤버 추상화에 적합한 인터페이스 방법은 내부 컬렉션에 쉽게 위임 할 수 있습니다.

성능 영향에 대해서는 항상 가독성을 높이기 위해 코드를 최적화하는 것이 좋습니다. 성능 영향이 의심되는 경우 코드를 프로파일 링하여 두 구현을 비교하십시오.


4

다른 답변은 +1입니다. 또한 실제로 DDD (Domain Driven Design) 커뮤니티에서 실제로는 우수 사례로 간주됩니다. 그들은 귀하의 도메인과 도메인과의 상호 작용이 기본 데이터 구조가 아닌 의미 적 도메인 의미를 가져야한다고 주장합니다. A Map<String, Widget>는 캐시 일 수 있지만 다른 일 일 수도 있습니다 .IMNSHO (My Not So Humble Opinion)에서 올바르게 수행 한 작업은 컬렉션이 나타내는 것 (이 경우 캐시)을 모델링하는 것입니다.

기본 데이터 구조를 둘러싼 도메인 클래스 래퍼에는 데이터 구조가 아닌 상호 작용이 가능한 도메인 클래스를 실제로 만드는 다른 멤버 변수 또는 함수가 있어야한다는 점에서 중요한 편집을 추가 할 것입니다 (Java에만 값 유형이있는 경우) , Java 10으로 가져올 것입니다-약속합니다!)

Java 8의 스트림 이이 모든 것에 어떤 영향을 미치는지 보는 것이 흥미로울 것입니다. 일부 공용 인터페이스는 Java 객체와 달리 Stream (일반 Java 기본 또는 문자열 삽입)을 처리하는 것을 선호한다고 상상할 수 있습니다.

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