인터페이스 디자인에서 제네릭을 사용하는 경우


11

미래에 타사가 구현할 인터페이스가 있으며 기본 구현을 직접 제공합니다. 예제를 보여주기 위해 커플 만 사용할 것입니다.

현재는 다음과 같이 정의됩니다

안건:

public interface Item {

    String getId();

    String getName();
}

품목 스택 :

public interface ItemStackFactory {

    ItemStack createItemStack(Item item, int quantity);
}

ItemStackContainer :

public interface ItemStackContainer {

    default void add(ItemStack stack) {
        add(stack, 1);
    }

    void add(ItemStack stack, int quantity);
}

이제 ItemItemStackFactory나는 절대적으로 미래에 그것을 확장 할 필요가 일부 타사을 예견 할 수있다. ItemStackContainer제공된 확장 구현 외부에서 예측할 수있는 방식으로는 향후 확장 될 수도 있습니다.

이제이 라이브러리를 최대한 강력하게 만들려고합니다. 이것은 아직 초기 단계 (pre-pre-alpha)에 있기 때문에 이것은 과도한 엔지니어링 (YAGNI) 행위 일 수 있습니다. 제네릭을 사용하기에 적절한 장소입니까?

public interface ItemStack<T extends Item> {

    T getItem();

    int getQuantity();
}

public interface ItemStackFactory<T extends ItemStack<I extends Item>> {

    T createItemStack(I item, int quantity);
}

이것이 구현과 사용법을 읽고 이해하기가 더 어려워 질까 걱정됩니다. 가능하면 중첩 제네릭을 피하는 것이 좋습니다.


1
Kinda는 구현 세부 정보를 어느 쪽이든 노출시키는 것처럼 느낍니다. 발신자가 단순한 수량이 아닌 스택에 대해 작업하고 알고 / 관리해야하는 이유는 무엇입니까?
cHao

그건 내가 생각하지 못한 것입니다. 이 특정 문제에 대한 좋은 통찰력을 제공합니다. 코드를 변경해야합니다.
Zymus

답변:


9

구현이 일반적인 경우 인터페이스에서 제네릭을 사용합니다.

예를 들어, 임의의 객체를 수용 할 수있는 모든 데이터 구조는 일반 인터페이스에 적합합니다. 예 : List<T>Dictionary<K,V>.

일반화 된 방식으로 형식 안전성을 향상시키려는 상황은 제네릭에 적합합니다. A List<String>는 문자열에서만 작동하는 목록입니다.

일반화 된 방식으로 SRP를 적용하고 유형별 방법을 사용하지 않으려는 상황은 제네릭에 적합합니다. 예를 들어 PersonDocument, PlaceDocument 및 ThingDocument는로 대체 될 수 있습니다 Document<T>.

추상 팩토리 패턴은 제네릭에 대한 유스 케이스이며 일반적인 팩토리 메소드는 일반적이고 구체적인 인터페이스에서 상속되는 오브젝트를 작성합니다.


나는 일반 인터페이스가 일반 구현과 쌍을 이루어야한다는 생각에 실제로 동의하지 않습니다. 인터페이스에서 제네릭 형식 매개 변수를 선언 한 다음 다양한 구현에서이를 구체화하거나 인스턴스화하는 것은 강력한 기술입니다. 스칼라에서 특히 흔합니다. 인터페이스 추상적 인 것들; 수업은 그들을 전문화합니다.
Benjamin Hodgson

@ BenjaminHodgson : 특정 유형의 일반 인터페이스에서 구현을 생성한다는 의미입니다. 전반적인 접근 방식은 여전히 ​​일반적이지만 다소 적습니다.
Robert Harvey

나는 동의한다. 아마도 나는 당신의 대답을 오해했을 것입니다-당신은 그런 종류의 디자인에 대해 조언하는 것처럼 보였습니다.
Benjamin Hodgson

@ BenjaminHodgson : 팩토리 메소드가 일반적인 인터페이스가 아닌 구체적인 인터페이스 에 대해 작성되지 않습니까? (Abstract Factory는 일반적인 것이지만)
Robert Harvey

나는 우리가 동의한다고 생각합니다 :) 아마도 OP의 예를 다음과 같이 쓸 것입니다 class StackFactory { Stack<T> Create<T>(T item, int count); }. 더 명확한 설명을 원한다면 답변에 "하위 클래스의 유형 매개 변수를 정의하는 것"이라는 의미의 예를 작성할 수 있지만 대답은 원래 질문과 접하게 관련이 있습니다.
Benjamin Hodgson

8

인터페이스에 일반성을 도입하는 방법에 대한 계획이 맞습니다. 그러나 이것이 좋은 아이디어인지 아닌지에 대한 귀하의 질문에는 다소 복잡한 답변이 필요합니다.

수년 동안 저는 제가 일한 회사를 대신하여 소프트웨어 엔지니어링 직책에 대한 여러 후보자를 인터뷰했으며, 구직자들의 대다수가 일반 수업 을 사용하는 것이 편하다는 것을 깨달았 습니다. 일반 컬렉션 클래스는 작성 하지 않습니다 . 대부분의 커리어에서 하나의 일반 클래스를 작성한 적이 없으며, 클래스를 작성하도록 요청하면 완전히 잃어버린 것처럼 보입니다. 따라서 인터페이스를 구현하거나 기본 구현을 확장하려는 사람에게 제네릭을 강제로 사용하면 사람들을 해고 할 수 있습니다.

그러나 당신이 시도 할 수있는 것은 일반 및 비 제네릭 버전의 인터페이스와 기본 구현을 모두 제공하여 제네릭을 사용하지 않는 사람들은 좁은 유형 캐스트가 많은 세상에서 행복하게 살 수 있습니다. 제네릭은 언어에 대한 폭 넓은 이해의 이점을 얻을 수 있습니다.


3

이제 ItemItemStackFactory나는 절대적으로 미래에 그것을 확장 할 필요가 일부 타사을 예견 할 수있다.

당신을위한 결정이 있습니다. 제네릭을 제공하지 않으면 다음 Item을 사용할 때마다 구현으로 업 캐스트해야 합니다.

class MyItem implements Item {
}
MyItem item = new MyItem();
ItemStack is = createItemStack(item, 10);
// They would have to cast here.
MyItem theItem = (MyItem)is.getItem();

적어도 ItemStack제안하는 방식으로 일반화해야합니다. 제네릭의 가장 큰 장점은 거의 아무 것도 캐스트 할 필요가 없으며 사용자가 캐스트하도록 강제하는 코드를 제공하는 것은 범죄 행위입니다.


2

와우 ... 나는 이것에 대해 완전히 다른 것을 가지고있다.

제 3 자에게 필요한 것을 절대적으로 예견 할 수 있습니다

이것은 실수입니다. "미래를위한"코드를 작성해서는 안됩니다.

또한 인터페이스는 하향식으로 작성됩니다. 클래스를 보지 않고 "이 클래스는 인터페이스를 완전히 구현 한 다음 해당 인터페이스를 다른 곳에서도 사용할 수 있습니다"라고 말합니다. 인터페이스를 사용하는 클래스 만이 인터페이스가 무엇인지 정확히 알고 있기 때문에 이는 잘못된 접근법입니다. 대신 "이 시점에서 내 클래스에는 종속성이 필요합니다. 해당 종속성에 대한 인터페이스를 정의하겠습니다.이 클래스 작성을 마치면 해당 종속성의 구현을 작성합니다." 누군가가 인터페이스를 재사용하고 기본 구현을 자체 인터페이스로 대체할지 여부는 전적으로 관련이 없습니다. 그들은 결코 할 수 없습니다. 인터페이스가 쓸모 없다는 의미는 아닙니다. 코드가 Inversion of Control 원칙을 따르도록하여 여러 곳에서 코드를 확장 할 수있게합니다. "


0

귀하의 상황에서 제네릭을 사용하는 것은 너무 복잡하다고 생각합니다.

인터페이스의 제네릭 버전을 선택하면 디자인에 다음과 같이 표시됩니다.

  1. ItemStackContainer <A>는 단일 유형의 스택 A를 포함합니다 (예 : ItemStackContainer는 여러 유형의 스택을 포함 할 수 없음).
  2. ItemStack은 특정 유형의 항목 전용입니다. 따라서 모든 종류의 스택에는 추상화 유형이 없습니다.
  3. 각 유형의 항목에 대해 특정 ItemStackFactory를 정의해야합니다. 팩토리 패턴으로 너무 좋은 것으로 간주됩니다.
  4. ItemStack <?과 같은 더 어려운 코드를 작성하십시오. 항목>을 확장하여 유지 관리 성을 줄입니다.

이러한 암시 적 가정과 제한은 신중하게 결정해야 할 매우 중요한 사항입니다. 따라서 특히 초기 단계에서 이러한 조기 설계를 도입해서는 안됩니다. 간단하고 이해하기 쉬운 일반적인 디자인이 필요합니다.


0

나는 주로이 질문에 달려 있다고 말합니다. 누군가 Item및 의 기능을 확장 해야하는 경우 ItemStackFactory,

  • 메소드를 덮어 쓰므로 하위 클래스의 인스턴스가 기본 클래스처럼 사용되며 다르게 작동 합니까?
  • 또는 회원을 추가 할 것이다 사용하는 다른 서브 클래스를?

첫 번째 경우에는 제네릭이 필요하지 않으며 두 번째 경우에는있을 수 있습니다.


0

아마도 Foo는 하나의 인터페이스이고 Bar는 또 다른 인터페이스 인 인터페이스 계층 구조를 가질 수 있습니다. 유형이 둘 다 여야 할 때마다 FooAndBar입니다.

과도한 desgin을 피하려면 필요할 때만 FooAndBar 유형을 만들고 아무것도 확장하지 않는 인터페이스 목록을 유지하십시오.

이것은 대부분의 프로그래머에게 훨씬 편안합니다 (대부분 유형 검사 또는 정적 정적 입력을 다루지 않는 경우). 자신의 제네릭 클래스를 작성한 사람은 거의 없습니다.

또한 참고 : 인터페이스는 가능한 조치를 실행할 수있는 것에 관한 것입니다. 실제로는 명사가 아닌 동사 여야합니다.


이것은 제네릭을 사용하는 대신 사용할 수 있지만 원래 질문은 제안한 것보다 제네릭을 언제 사용할 지에 대한 질문 이었습니다. 제네릭이 필요하지 않다고 제안하기 위해 답변을 향상시킬 수 있습니까?하지만 여러 인터페이스를 사용하는 것이 모든 사람의 대답입니까?
Jay Elston
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.