마커 인터페이스의 목적은 무엇입니까?


답변:


77

이것은 "Mitch Wheat"의 응답을 기반으로 한 약간의 접선입니다.

일반적으로 사람들이 프레임 워크 디자인 지침을 인용하는 것을 볼 때마다 항상 다음과 같이 언급하고 싶습니다.

일반적으로 대부분의 경우 프레임 워크 디자인 지침을 무시해야합니다.

이것은 프레임 워크 디자인 가이드 라인의 문제 때문이 아닙니다. .NET 프레임 워크는 환상적인 클래스 라이브러리라고 생각합니다. 이러한 환상의 대부분은 프레임 워크 디자인 지침에서 비롯됩니다.

그러나 설계 지침은 대부분의 프로그래머가 작성한 대부분의 코드에 적용되지 않습니다. 그들의 목적은 라이브러리 작성을보다 효율적으로 만드는 것이 아니라 수백만 명의 개발자가 사용하는 대규모 프레임 워크를 만들 수 있도록하는 것입니다.

많은 제안이 다음과 같은 작업을 수행하도록 안내 할 수 있습니다.

  1. 무언가를 구현하는 가장 간단한 방법이 아닐 수도 있습니다.
  2. 추가 코드 중복이 발생할 수 있습니다.
  3. 추가 런타임 오버 헤드가있을 수 있습니다.

.net 프레임 워크는 정말 큽니다. 너무 커서 누군가가 그것의 모든 측면에 대해 상세한 지식을 가지고 있다고 가정하는 것은 절대적으로 불합리합니다. 사실, 대부분의 프로그래머가 이전에 사용한 적이없는 프레임 워크의 일부를 자주 접한다고 가정하는 것이 훨씬 안전합니다.

이 경우 API 디자이너의 주요 목표는 다음과 같습니다.

  1. 나머지 프레임 워크와 일관되게 유지
  2. API 표면 영역에서 불필요한 복잡성 제거

프레임 워크 디자인 지침은 개발자가 이러한 목표를 달성하는 코드를 만들도록합니다.

즉, 코드 복제를 의미하는 경우에도 상속 계층을 피하거나 공유 도우미를 사용하는 것보다 모든 예외 발생 코드를 "진입 점"으로 푸시하는 것과 같은 작업을 수행해야합니다 (디버거에서 스택 추적이 더 의미가 있음). 다른 유사한 것들의.

이러한 지침에서 마커 인터페이스 대신 속성 사용을 제안하는 주된 이유는 마커 인터페이스를 제거하면 클래스 라이브러리의 상속 구조에 훨씬 더 접근하기 쉽기 때문입니다. 30 개 유형과 6 개 계층의 상속 계층이있는 클래스 다이어그램은 15 개 유형과 2 개 계층의 계층 구조를 가진 클래스 다이어그램에 비해 매우 어렵습니다.

실제로 API를 사용하는 개발자가 수백만 명이거나 코드 기반이 정말 큰 경우 (예 : 100,000 개 이상의 LOC) 이러한 지침을 따르는 것이 많은 도움이 될 수 있습니다.

5 백만 명의 개발자가 API를 배우는 데 60 분을 소비하지 않고 15 분 동안 API를 배우는 데 소비하면 결과적으로 428 인년이 절약됩니다. 그것은 많은 시간입니다.

그러나 대부분의 프로젝트에는 수백만 명의 개발자 또는 10 만 이상의 LOC가 포함되지 않습니다. 4 명의 개발자와 약 50K loc이있는 일반적인 프로젝트에서는 가정 집합이 많이 다릅니다. 팀의 개발자는 코드 작동 방식을 훨씬 더 잘 이해할 것입니다. 즉, 고품질 코드를 신속하게 생성하고 버그 수와 변경에 필요한 노력을 줄이기 위해 최적화하는 것이 훨씬 더 합리적입니다.

.net 프레임 워크와 일치하는 코드를 개발하는 데 1 주를 소비하고 변경하기 쉽고 버그가 적은 코드를 작성하는 데 8 시간을 소비하면 다음과 같은 결과가 발생할 수 있습니다.

  1. 늦은 프로젝트
  2. 낮은 보너스
  3. 버그 수 증가
  4. 사무실에서 더 많은 시간을 보내고 해변에서 마가리타를 마시는 시간은 줄었습니다.

4,999,999 명의 다른 개발자가 비용을 흡수하지 않으면 일반적으로 가치가 없습니다.

예를 들어 마커 인터페이스에 대한 테스트는 단일 "is"표현식으로 귀결되며 속성을 찾는 코드가 줄어 듭니다.

그래서 제 조언은 :

  1. 광범위하게 사용되는 클래스 라이브러리 (또는 UI 위젯)를 개발하는 경우 프레임 워크 지침을 종교적으로 따르십시오.
  2. 프로젝트에 10 만 개 이상의 LOC가있는 경우 일부를 채택하는 것이 좋습니다.
  3. 그렇지 않으면 완전히 무시하십시오.

12
개인적으로 나중에 사용해야하는 라이브러리로 작성한 코드를 봅니다. 나는 소비가 널리 퍼져 있든 없든 상관하지 않습니다. 지침을 따르면 일관성이 향상되고 몇 년 후 코드를보고 이해할 필요가있을 때 놀라움이 줄어 듭니다 ...
Reed Copsey

16
나는 지침이 나쁘다는 것을 말하는 것이 아닙니다. 코드베이스의 크기와 보유한 사용자 수에 따라 달라야한다고 말하고 있습니다. 많은 디자인 가이드 라인은 바이너리 비교 성을 유지하는 것과 같은 것에 기반을두고 있습니다. 이것은 BCL과 같은 것만 큼 많은 프로젝트에서 사용하는 "내부"라이브러리에 중요하지 않습니다. 유용성과 관련된 지침과 같은 다른 지침은 거의 항상 중요합니다. 교훈은 특히 소규모 프로젝트에서 지침에 대해 지나치게 종교적이지 않는 것입니다.
Scott Wisniewski

6
+1-OP의 질문에 답하지 못함-MI의 목적-그럼에도 불구하고 매우 도움이됩니다.
bzarah

5
@ScottWisniewski : 심각한 요점을 놓치고 있다고 생각합니다. 프레임 워크 지침은 대규모 프로젝트에는 적용되지 않으며 중형 및 일부 소규모 프로젝트에 적용됩니다. Hello-World 프로그램에 항상 적용하려고하면 과잉 죽이게됩니다. 예를 들어 인터페이스를 5 개의 메서드로 제한하는 것은 앱 크기에 관계없이 항상 좋은 경험 법칙입니다. 또 하나 놓친 것은 오늘의 작은 앱이 내일의 큰 앱이 될 수 있다는 것입니다. 따라서 대규모 앱에 적용되는 좋은 원칙을 염두에두고 빌드하는 것이 좋습니다. 따라서 확장 할 때 많은 코드를 다시 작성할 필요가 없습니다.
Phil

2
디자인 가이드 라인을 (대부분) 따르는 것이 어떻게 갑자기 1 주가 걸리는 8 시간 프로젝트로 이어질지 잘 모르겠습니다. 예 : 대신 virtual protected템플릿 메서드 DoSomethingCore를 명명하는 DoSomething것은 그다지 추가 작업이 아니고 템플릿 메서드 라는 것을 분명히 전달합니다 ... IMNSHO, API를 고려하지 않고 애플리케이션을 작성하는 사람들 ( But.. I'm not a framework developer, I don't care about my API!)은 정확히 많은 중복을 작성하는 사람들입니다 ( 또한 문서화되지 않고 일반적으로 읽을 수없는) 코드입니다.
Laoujin 2015 년

44

마커 인터페이스는 런타임에 특정 인터페이스를 구현하는 것으로 클래스의 기능을 표시하는 데 사용됩니다.

인터페이스 디자인.NET 형 디자인 가이드 라인 - 인터페이스 디자인은 C #으로 속성을 사용 찬성 마커 인터페이스의 사용을 억제하지만, @Jay Bazuzi 지적, 속성보다 마커 인터페이스를 확인하기 쉽습니다 :o is I

그래서이 대신 :

public interface IFooAssignable {} 

public class FooAssignableAttribute : IFooAssignable 
{
    ...
}

.NET 지침에서는 다음과 같이 권장합니다.

public class FooAssignableAttribute : Attribute 
{
    ...
}

[FooAssignable]
public class Foo 
{    
   ...
} 

27
또한 마커 인터페이스와 함께 제네릭을 완전히 사용할 수 있지만 속성은 사용할 수 없습니다.
Jordão

18
나는 속성과 선언적 관점에서 어떻게 보이는지 좋아하지만 런타임에 일류 시민이 아니며 작업하기 위해 상당한 양의 상대적으로 낮은 수준의 배관이 필요합니다.
Jesse C. Slicer

4
@ Jordão-이것은 정확히 내 생각이었습니다. 예를 들어, 데이터베이스 액세스 코드를 추상화하려는 경우 (예 : Linq에서 Sql로) 공통 인터페이스를 사용하면 훨씬 쉬워집니다. 사실, 속성으로 캐스팅 할 수없고 제네릭에서 사용할 수 없기 때문에 속성으로 그런 종류의 추상화를 작성하는 것이 가능하지 않을 것이라고 생각합니다. 다른 클래스가 모두 파생되는 빈 기본 클래스를 사용할 수 있다고 가정하지만 빈 인터페이스를 갖는 것과 다소 비슷하게 느껴집니다. 또한 나중에 공유 기능이 필요하다는 사실을 깨닫는 경우 메커니즘이 이미 마련되어 있습니다.
tandrewnichols

23

다른 모든 대답은 "피해야한다"고 말 했으므로 그 이유를 설명하는 것이 유용 할 것입니다.

첫째, 마커 인터페이스가 사용되는 이유 :이를 구현하는 객체를 사용하는 코드가 해당 인터페이스를 구현하는지 여부를 확인하고 해당하는 경우 객체를 다르게 처리 할 수 ​​있도록하기 위해 존재합니다.

이 접근 방식의 문제점은 캡슐화가 중단된다는 것입니다. 이제 개체 자체가 외부에서 사용되는 방식을 간접적으로 제어 할 수 있습니다. 더욱이, 그것은 그것이 사용될 시스템에 대한 지식을 가지고 있습니다. 마커 인터페이스를 적용함으로써, 클래스 정의는 그것이 마커의 존재를 확인하는 어딘가에서 사용될 것으로 예상하고 있음을 제안합니다. 사용되는 환경에 대한 암묵적인 지식을 가지고 있으며 사용 방법을 정의하려고합니다. 이것은 캡슐화의 개념에 위배됩니다. 왜냐하면 그것은 완전히 자체 범위 밖에 존재하는 시스템의 일부 구현에 대한 지식을 가지고 있기 때문입니다.

실질적인 수준에서 이것은 이식성과 재사용 성을 감소시킵니다. 클래스가 다른 응용 프로그램에서 다시 사용되는 경우 인터페이스도 복사되어야하며 새 환경에서는 의미가 없어 완전히 중복 될 수 있습니다.

따라서 "마커"는 클래스에 대한 메타 데이터입니다. 이 메타 데이터는 클래스 자체에서 사용되지 않으며 특정 방식으로 개체를 처리 할 수 ​​있도록 외부 클라이언트 코드에만 의미가 있습니다. 클라이언트 코드에만 의미가 있기 때문에 메타 데이터는 클래스 API가 아닌 클라이언트 코드에 있어야합니다.

는 "마커 인터페이스"와 정상적인 인터페이스의 차이는 방법과 인터페이스가 어떻게 외부 세계에 알 수 있다는 것입니다 빈 인터페이스가 어떻게 외부 세계를 말하고 의미하는 반면에 사용할 수 있어야 사용할 수 있습니다.


1
모든 인터페이스의 주요 목적은 해당 인터페이스와 관련된 계약을 준수하겠다고 약속하는 클래스와 그렇지 않은 클래스를 구분하는 것입니다. 인터페이스는 계약을 이행하는 데 필요한 모든 구성원의 호출 서명을 제공 할 책임도 있지만 특정 인터페이스를 특정 클래스에서 구현해야하는지 여부를 결정하는 것은 구성원이 아니라 계약입니다. 계약에 정적 멤버가있는 경우 에만 IConstructableFromString<T>클래스를 T구현할 수 있다고 명시되어있는 IConstructableFromString<T>경우 ...
supercat

... public static T ProduceFromString(String params);인터페이스의 동반 클래스는 메소드를 제공 할 수 있습니다 public static T ProduceFromString<T>(String params) where T:IConstructableFromString<T>. 클라이언트 코드에와 같은 메서드가있는 경우 클라이언트 코드 T[] MakeManyThings<T>() where T:IConstructableFromString<T>를 수정하지 않고도 클라이언트 코드와 함께 작동 할 수있는 새로운 유형을 정의 할 수 있습니다. 메타 데이터가 클라이언트 코드에 있으면 기존 클라이언트에서 사용할 새 유형을 만들 수 없습니다.
supercat

그러나 계약 T과 그것을 사용하는 클래스 IConstructableFromString<T>는 인터페이스에 일부 동작을 설명하는 메서드가 있으므로 마커 인터페이스가 아닙니다.
Tom B

클래스에 필요한 정적 메서드는 인터페이스의 일부가 아닙니다. 인터페이스의 정적 멤버는 인터페이스 자체에 의해 구현됩니다. 인터페이스가 구현 클래스의 정적 멤버를 참조하는 방법은 없습니다.
supercat

리플렉션을 사용하여 메서드가 제네릭 형식에 특정 정적 메서드가 있는지 여부를 확인하고 해당 메서드가있는 경우 해당 메서드를 실행할 수 있지만 ProduceFromString위의 예제에서 정적 메서드를 검색하고 실행하는 실제 프로세스 에는 관련이 없습니다. 인터페이스는 필요한 기능을 구현하기 위해 어떤 클래스가 예상되어야 하는지를 나타내는 마커로 사용된다는 점을 제외하고는 어떤 방식 으로든 인터페이스.
supercat

8

마커 인터페이스는 언어가 구별 된 공용체 유형을 지원하지 않을 때 때때로 필요한 악이 될 수 있습니다.

형식이 A, B 또는 C 중 하나 여야하는 인수를 예상하는 메서드를 정의한다고 가정합니다. 많은 함수 우선 언어 (예 : F # )에서 이러한 형식은 다음과 같이 명확하게 정의 할 수 있습니다.

type Arg = 
    | AArg of A 
    | BArg of B 
    | CArg of C

그러나 C #과 같은 OO 우선 언어에서는 불가능합니다. 여기에서 비슷한 것을 달성하는 유일한 방법은 인터페이스 IArg를 정의하고 A, B 및 C를 "표시"하는 것입니다.

물론 "객체"유형을 인수로 받아들이면 마커 인터페이스 사용을 피할 수 있지만 표현력과 유형 안전성을 잃게됩니다.

차별적 인 공용체 유형은 매우 유용하며 적어도 30 년 동안 기능 언어로 존재 해 왔습니다. 이상하게도 오늘날까지 모든 주류 OO 언어는이 기능을 무시했습니다. 실제로 함수형 프로그래밍과는 아무런 관련이 없지만 유형 시스템에 속합니다.


a Foo<T>는 모든 유형에 대해 별도의 정적 필드 집합 T을 가지기 때문에 제네릭 클래스에 a를 처리하는 대리자를 포함하는 정적 필드를 포함 T하고 클래스가있는 모든 유형을 처리하는 함수로 해당 필드를 미리 채우는 것은 어렵지 않습니다. 함께 일해야합니다. 유형에 대한 제네릭 인터페이스 제약 조건을 사용 T하면 제공된 유형이 실제로 유효한지 확인할 수는 없지만 제공된 유형이 적어도 유효하다고 주장하는지 컴파일러 시간에 확인합니다.
supercat

6

마커 인터페이스는 비어있는 인터페이스입니다. 클래스는이 인터페이스를 어떤 이유로 사용할 메타 데이터로 구현합니다. C #에서는 다른 언어에서 마커 인터페이스를 사용하는 것과 같은 이유로 클래스를 마크 업하는 데 더 일반적으로 특성을 사용합니다.


4

마커 인터페이스를 사용하면 모든 하위 클래스에 적용되는 방식으로 클래스에 태그를 지정할 수 있습니다. "순수한"마커 인터페이스는 아무것도 정의하거나 상속하지 않습니다. 더 유용한 유형의 마커 인터페이스는 다른 인터페이스를 "상속"하지만 새 멤버를 정의하지 않는 인터페이스 일 수 있습니다. 예를 들어, "IReadableFoo"인터페이스가있는 경우 "Foo"처럼 동작하는 "IImmutableFoo"인터페이스도 정의 할 수 있지만이를 사용하는 모든 사람에게 값을 변경하지 않을 것이라고 약속합니다. IImmutableFoo를 허용하는 루틴은 IReadableFoo처럼 사용할 수 있지만 루틴은 IImmutableFoo를 구현하는 것으로 선언 된 클래스 만 허용합니다.

"순수한"마커 인터페이스에 대한 많은 용도는 생각할 수 없습니다. 내가 생각할 수있는 유일한 방법은 EqualityComparer (of T) .Default가 IDoNotUseEqualityComparer를 구현 한 모든 형식에 대해 Object.Equals를 반환하는 경우입니다. 형식도 IEqualityComparer를 구현 한 경우에도 마찬가지입니다. 이것은 Liskov Substitution Principle을 위반하지 않고 봉인되지 않은 불 변형을 가질 수있게합니다 : 만약 형이 동등성 테스트와 관련된 모든 메소드를 봉인한다면, 파생 형은 추가 필드를 추가하고 그것들을 변경 가능하게 할 수 있지만 그러한 필드의 변이는 t는 기본 유형 방법을 사용하여 표시됩니다. 봉인되지 않은 불변 클래스를 가지고 EqualityComparer.Default의 사용을 피하거나 IEqualityComparer를 구현하지 않는 파생 클래스를 신뢰하는 것은 끔찍하지 않을 수 있습니다.


4

이 두 가지 확장 방법은 Scott이 속성보다 마커 인터페이스를 선호한다고 주장하는 대부분의 문제를 해결합니다.

public static bool HasAttribute<T>(this ICustomAttributeProvider self)
    where T : Attribute
{
    return self.GetCustomAttributes(true).Any(o => o is T);
}

public static bool HasAttribute<T>(this object self)
    where T : Attribute
{
    return self != null && self.GetType().HasAttribute<T>()
}

이제 다음이 있습니다.

if (o.HasAttribute<FooAssignableAttribute>())
{
    //...
}

대:

if (o is IFooAssignable)
{
    //...
}

Scott이 주장한 것처럼 API를 빌드하는 데 첫 번째 패턴이 두 번째 패턴에 비해 5 배 더 오래 걸리는지 알 수 없습니다.


1
여전히 제네릭이 없습니다.
Ian Kemp

1

마커는 빈 인터페이스입니다. 마커가 있거나 없습니다.

class Foo : IConfidential

여기서 우리는 Foo를 기밀로 표시합니다. 실제 추가 속성이나 속성이 필요하지 않습니다.


0

마커 인터페이스는 본문 / 데이터 멤버 / 구현이없는 전체 빈 인터페이스입니다.
클래스의 구현에 필요한 마커 인터페이스, 그냥 "이다 마크 "; 이는 JVM에 특정 클래스가 복제 목적이므로 복제를 허용한다는 것을 의미합니다. 이 특정 클래스는 객체를 직렬화하는 것이므로 객체가 직렬화되도록 허용하십시오.


0

마커 인터페이스는 실제로 OO 언어로 된 절차 적 프로그래밍입니다. 인터페이스는 마커 인터페이스를 제외하고 구현 자와 소비자 간의 계약을 정의합니다. 마커 인터페이스는 그 자체 만 정의하기 때문입니다. 따라서 게이트에서 즉시 마커 인터페이스는 인터페이스라는 기본 목적에서 실패합니다.

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