이미 추상 클래스가 있다면 인터페이스를 정의하는 것이 합리적입니까?


12

기본 / 공유 기능이있는 클래스가 있습니다. 나는 그것을 위해 사용 abstract class한다 :

public interface ITypeNameMapper
{
    string Map(TypeDefinition typeDefinition);
}

public abstract class TypeNameMapper : ITypeNameMapper
{
    public virtual string Map(TypeDefinition typeDefinition)
    {
        if (typeDefinition is ClassDefinition classDefinition)
        {
            return Map(classDefinition);
        }
        ...

        throw new ArgumentOutOfRangeException(nameof(typeDefinition));
    }

    protected abstract string Map(ClassDefinition classDefinition);
}

보시다시피 인터페이스도 있습니다 ITypeNameMapper. 이미 추상 클래스가 TypeNameMapper있거나 abstract class충분 하다면이 인터페이스를 정의하는 것이 합리적 입니까?

TypeDefinition 이 최소한의 예에서도 추상입니다.


4
참고로 OOD에서 코드의 유형 확인은 클래스 계층 구조가 올바르지 않음을 나타냅니다. 확인중인 유형에서 파생 클래스를 작성하라는 메시지가 표시됩니다. 따라서이 경우 Map () 메서드를 지정하고 TypeDefinition 및 ClassDefinition 클래스 모두에서 해당 인터페이스를 구현하는 ITypeMapper 인터페이스가 필요합니다. 이렇게하면 ClassAssembler가 단순히 ITypeMappers 목록을 반복하여 각각에 Map ()을 호출하고 각각 고유 한 구현이 있기 때문에 두 유형에서 원하는 문자열을 가져옵니다.
Rodney P. Barbati

1
꼭 필요한 경우가 아니라면 인터페이스 대신 기본 클래스를 사용하는 것을 "베이스 굽기"라고합니다. 인터페이스로 수행 할 수있는 경우 인터페이스로 수행하십시오. 그런 다음 추상 클래스는 유용한 추가 요소가됩니다. artima.com/intv/dotnet.html 구체적으로, 원격 기능을 사용하려면 MarshalByRefObject에서 파생해야하므로 원격으로 연결하려면 다른 항목에서 파생 할 수 없습니다 .
Ben

상황에 따라 전환 할 수있는 동일한 유형의 매퍼를 많이 갖고 싶다면 @ RodneyP.Barbati가 작동하지 않습니다.
Konrad

1
@ 흥미로운 기지를 태워 버렸습니다. 보석 주셔서 감사합니다!
Konrad

@Konrad 그것은 틀 렸습니다-인터페이스의 다른 구현을 호출하여 인터페이스를 구현하는 것을 방해하는 것은 없습니다. 따라서 각 유형에는 유형의 구현을 통해 노출 가능한 플러그 가능한 구현이있을 수 있습니다.
Rodney P. Barbati

답변:


31

예, C # 은 인터페이스를 제외하고 다중 상속을 허용하지 않기 때문 입니다.

따라서 TypeNameMapper 및 SomethingelseMapper 인 클래스가 있으면 다음을 수행 할 수 있습니다.

class MultiFunctionalClass : ITypeNameMapper, ISomethingelseMapper 
{
    private TypeNameMapper map1
    private SomethingelseMapper map2

    public string Map(TypeDefinition typeDefinition) { return map1.Map(typeDefintion);}

    public string Map(OtherDef otherDef) { return map2.Map(orderDef); }
}

4
@Liath : 단일 책임은 실제로 상속이 필요한 이유에 대한 기여 요인입니다. 세 가지 특성을 고려 ICanFly, ICanRun, ICanSwim. 각 특성 조합 (YYY, YYN, YNY, ..., NNY, NNN)마다 하나씩 8 개의 생물 클래스를 생성해야합니다. SRP는 각 행동 (비행, 달리기, 수영)을 한 번만 구현 한 다음 8 가지 생물에 재사용 할 수 있음을 나타냅니다. 컴포지션은 여기에서 더 좋을지 모르지만 1 초 동안 컴포지션을 무시 하면 SRP로 인해 여러 개의 재사용 가능한 개별 동작 을 상속 / 구현해야 할 필요가 있습니다.
Flater

4
@ 콘라드 그게 내 뜻이야. 인터페이스를 작성하고 필요하지 않으면 비용은 3 LoC입니다. 인터페이스를 작성하지 않고 필요한 인터페이스를 찾으면 전체 응용 프로그램의 리팩토링 비용 이 발생할 수 있습니다.
Ewan

3
(1) 인터페이스 구현은 상속입니까? (2) 질문과 답변 모두 자격을 갖추어야합니다. "때에 따라 다르지." (3) 다중 상속에는 경고 스티커가 필요합니다. (4) 나는 당신에게 기발한 형식적인 interface과실 의 K를 보여줄 수 있습니다 . 독립적으로 진화하는 기본에 있어야하는 중복 구현!, 템플릿 메소드를 원하기 때문에이 정크를 몰아내는 조건부 논리 변경, 캡슐화 실패, 존재하지 않는 추상화. 내가 가장 좋아하는 것 : 1-method 클래스는 각각 interface하나의 인스턴스 만있는 자체 1-method를 구현 합니다. ... 등 등등
radarbob

3
코드에는 보이지 않는 마찰 계수가 있습니다. 불필요한 인터페이스를 읽어야 할 때 느끼게됩니다. 그런 다음 인터페이스 구현이 추상 클래스에 달하는 것처럼 우주 왕복선이 대기로 쏟아지는 것처럼 자체적으로 가열되는 것을 보여줍니다. 그것은 Twilight Zone이며 셔틀 도전자입니다. 나의 운명을 받아들이면서 나는 두려움없이 분리 된 경이로 성난 플라즈마를 본다. 변함없는 마찰은 완벽한 외관을 찢어내어 산산조각 난 헐크를 생산에 쏟아 붓습니다.
radarbob

4
@radarbob 시적. ^ _ ^ 동의합니다. 인터페이스 비용은 저렴하지만 존재하지 않습니다. 글을 쓰는 데는 그다지 많지 않지만 디버깅 상황에서는 실제 동작에 도달하는 1-2 단계가 더 있습니다. 도서관에서는 보통 그만한 가치가 있습니다. 그러나 응용 프로그램에서 리팩토링은 조기 일반화보다 낫습니다.
Errorsatz

1

인터페이스와 추상 클래스는 다른 용도로 사용됩니다.

  • 인터페이스 는 API를 정의하고 구현이 아닌 클라이언트에 속합니다.
  • 클래스가 구현을 공유하는 경우 추상 클래스의 이점을 얻을 수 있습니다 .

귀하의 예에서 interface ITypeNameMapper고객의 요구를 정의하고 abstract class TypeNameMapper가치를 추가하지 않습니다.


2
추상 클래스도 클라이언트에 속합니다. 그게 무슨 뜻인지 모르겠어요 public클라이언트에 속하는 모든 것 . TypeNameMapper구현자가 일반적인 논리를 작성하는 것을 막기 때문에 많은 가치를 추가합니다.
Konrad

2
인터페이스가 표시되는지 여부는 interfaceC #에서만 전체 다중 상속을 금지합니다.
중복 제거기

0

인터페이스의 전체 개념은 공유 API가있는 클래스 제품군을 지원하기 위해 작성되었습니다.

이것은 인터페이스를 사용하면 사양 구현이 둘 이상 (또는있을 것으로 예상 됨)을 의미한다고 명시 적으로 말하고 있습니다.

DI 프레임 워크는 많은 구현이있을지라도 인터페이스가 필요하다는 점에서 물을 흐릿하게 만들었습니다. 그것이 무엇입니까.

대답은 질문 자체에 있습니다. 추상 클래스가있는 경우 공통 API를 사용하여 둘 이상의 파생 클래스 작성을 준비하고 있습니다. 따라서 인터페이스 사용이 명확하게 표시됩니다.


2
"추상 클래스가 있다면 ... 인터페이스 사용이 명확하게 표시됩니다." 내 결론은 반대입니다 : 추상 클래스가있는 경우 인터페이스 인 것처럼 사용하십시오 (DI가 해당 클래스와 함께 재생되지 않으면 다른 구현을 고려하십시오). 실제로 작동하지 않는 경우에만 인터페이스를 작성하십시오. 나중에 언제든지 수행 할 수 있습니다.
maaartinus

1
디토, @maaartinus. 심지어 "인터페이스 인 것처럼" 클래스의 공개 회원 입니다 인터페이스. 또한 "나중에 언제든지 할 수있다"는 의견도있다. 이것은 디자인 기초 101의 핵심으로 간다.
radarbob

@maaartinus : 처음부터 인터페이스를 만들지 만 추상 클래스 유형의 참조를 사용하는 경우 인터페이스의 존재는 도움이되지 않습니다. 그러나 클라이언트 코드가 가능할 때마다 추상 클래스 유형 대신 인터페이스 유형을 사용하는 경우 추상 클래스와 내부적으로 매우 다른 구현을 생성해야하는 경우 작업을 크게 촉진합니다. 이 시점에서 인터페이스를 사용하기 위해 클라이언트 코드를 변경하는 것은 처음부터 클라이언트 코드를 디자인하는 것보다 훨씬 고통 스러울 것입니다.
supercat

1
@ supercat 라이브러리를 작성한다고 가정 할 수 있습니다. 응용 프로그램 인 경우 모든 발생을 한 번에 바꾸는 데 아무런 문제가 없습니다. 내 이클립스 (C #이 아닌 Java)로 할 수는 있지만 그렇게 할 수 없다면 find ... -exec perl -pi -e ...사소한 컴파일 오류와 비슷 하고 수정하는 것입니다. 내 요점은 : (현재) 쓸모없는 것을 갖지 않는다는 이점은 미래의 가능한 저축보다 훨씬 큽니다.
maaartinus

첫 번째 문장은 interface코드로 표시 한 경우 ( interface클래스 / 함수 등의 추상 인터페이스가 아닌 C #에 대해 이야기 하고 있음) 종료 한 후 "... 계승." interfaceMI가 있다면 s 가 필요 없습니다 .
중복 제거기

0

우리가이 질문에 명시 적으로 대답하고 싶다면 저자는 "이미 추상 클래스 TypeNameMapper를 가지고 있거나 추상 클래스만으로 충분하다면이 인터페이스를 정의하는 것이 합리적입니까?"라고 말합니다.

대답은 예, 아니오입니다-예, 이미 추상 기본 클래스를 가지고 있더라도 (클라이언트 코드에서 추상 기본 클래스를 참조하면 안 됨) 인터페이스를 작성해야합니다. 인터페이스가없는 추상 기본 클래스

빌드하려는 API에 대해 충분히 생각하지 않았다는 것은 분명합니다. 먼저 API를 제시 한 다음 원하는 경우 부분 구현을 제공하십시오. 추상 기본 클래스가 코드에서 유형 검사를 수행한다는 사실이 그것이 올바른 추상화가 아니라는 것을 알려주기에 충분합니다.

OOD의 대부분의 경우와 마찬가지로 그루브에 있고 객체 모델을 잘 수행하면 코드는 다음에 무엇이 올 것인지 알려줍니다. 인터페이스로 시작하여 필요한 클래스에서 해당 인터페이스를 구현하십시오. 비슷한 코드를 작성하고 있다면 추상 기본 클래스로 추출하십시오. 중요한 인터페이스, 추상 기본 클래스는 단지 도우미이며 둘 이상이있을 수 있습니다.


-1

나머지 응용 프로그램을 모르면 대답하기가 매우 어렵습니다.

어떤 종류의 DI 모델을 사용하고 있고 가능한 한 확장 가능하도록 코드를 설계했다고 가정하면 ( TypeNameMapper쉽게 새 항목을 추가 할 수 있음) 예라고 대답합니다.

이유 :

  • 기본 클래스가 구현하므로 interface자식 구현에서 걱정할 필요가 없으므로 효과적으로 무료로 얻을 수 있습니다.
  • 대부분의 IoC 프레임 워크 및 Mocking 라이브러리는을 기대 interface합니다. 그들 중 많은 사람들이 추상 클래스로 작업하는 동안 항상 주어진 것은 아니며 가능한 한 도서관 사용자의 다른 99 %와 같은 경로를 따르는 것을 믿는 사람입니다.

반대 이유 :

  • 엄밀히 말하면 (당신이 지적했듯이) 당신은 정말로 할 필요가 없습니다.
  • class/가 interface변경 되면 약간의 추가 오버 헤드가 있습니다.

고려한 모든 것은 interface. 이에 대한 이유는 무시할 만하지 만 조롱과 IoC에서 초록을 사용할 수는 있지만 인터페이스를 사용하는 것이 훨씬 쉽습니다. 궁극적으로, 나는 다른 방향으로 간 동료의 코드를 비판하지 않을 것입니다.

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