타입 테스트는 언제 가능합니까?


53

고유 한 유형 안전성을 가진 언어를 가정하면 (예 : JavaScript가 아님) :

a를 받아들이는 방법이 주어지면 SuperType대부분의 경우 유형 테스트를 수행하여 작업을 선택하려는 유혹을받을 수 있습니다.

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    o.doSomethingA()
  } else {
    o.doSomethingB();
  }
}

항상 그렇지는 않지만 일반적으로 하나의 재정의 가능한 메소드를 작성하고 다음 SuperType을 수행해야합니다.

public void DoSomethingTo(SuperType o) {
  o.doSomething();
}

... 각 하위 유형에는 자체 doSomething()구현 이 제공 됩니다. 우리의 응용 프로그램의 나머지 부분은 주어진 SuperType것이 실제로 a SubTypeA인지 또는 a 인지를 알지 못할 수 있습니다 SubTypeB.

훌륭한.

그러나 우리는 여전히 is a모든 타입 안전 언어에서 대부분 같은 작업을 수행하고 있습니다. 그리고 이는 명시 적 유형 테스트가 필요하다는 것을 암시합니다.

그럼, 상황이있는 경우, 해야 우리 또는 있어야 우리는 명시 적 유형의 테스트를 수행?

결석 한 마음이나 창의력 부족을 용서하십시오. 나는 내가 전에 한 일을 알고있다. 그러나 솔직히 오래 전에 내가 한 일이 좋았는지 기억할 수 없었습니다! 그리고 최근의 기억에서 카우보이 JavaScript 외부에서 유형을 테스트 해야 한다고 생각하지 않습니다 .



4
Java와 C # 모두 첫 번째 버전에서 제네릭을 가지고 있지 않다는 것을 지적 할 가치가 있습니다. 컨테이너를 사용하기 위해 Object로 캐스트해야했습니다.
Doval

6
"유형 검사"는 거의 항상 코드가 언어의 정적 타이핑 규칙을 준수하는지 검사하는 것을 말합니다. 당신이 의미하는 것은 보통 타입 테스팅 이라고합니다 .


3
이것이 내가 C ++를 작성하고 RTTI를 끄는 것을 좋아하는 이유입니다. 문자 그대로 런타임에 객체 유형을 테스트 할 수없는 경우 개발자는 여기서 묻는 질문과 관련하여 우수한 OO 디자인을 강요합니다.

답변:


48

"Never"는 "형식 테스트가 언제 괜찮습니까?"에 대한 정식 답변입니다. 이것을 증명하거나 반증 할 방법이 없습니다. "좋은 디자인"또는 "좋은 객체 지향 디자인"을 만드는 것에 대한 신념 체계의 일부입니다. 또한 Hokum입니다.

확실하게, 통합 된 클래스 세트와 그러한 종류의 직접 유형 테스트가 필요한 하나 또는 두 개 이상의 함수가 있다면 아마도 잘못하고있을 것입니다. 실제로 필요한 것은 다르게 구현되는 방법 SuperType과 하위 유형입니다. 이것은 객체 지향 프로그래밍의 일부이며, 클래스와 상속이 존재하는 모든 이유입니다.

이 경우 명시 적으로 형식 테스트는 형식 테스트가 본질적으로 잘못 되었기 때문에가 아니라 언어에 이미 유형 차별을 수행하는 깨끗하고 확장 가능하며 관용적 인 방법이있어 사용하지 않았기 때문에 잘못되었습니다. 대신에, 당신은 원시적이고 깨지기 쉬운 확장 할 수없는 관용구로 돌아 왔습니다.

해결책 : 관용구를 사용하십시오. 제안한대로 각 클래스에 메소드를 추가 한 후 표준 상속 및 메소드 선택 알고리즘이 적용되는 케이스를 판별하도록하십시오. 또는 기본 유형을 변경할 수없는 경우 서브 클래스를 작성하고 메소드를 추가하십시오.

기존의 지혜와 일부 답변에 대해서는 너무 많습니다. 명시 적 유형 테스트가 적합한 경우 :

  1. 일회성입니다. 유형 구분이 많이 필요한 경우 유형 또는 서브 클래스를 확장 할 수 있습니다. 그러나 당신은하지 않습니다. 명시 적 테스트가 필요한 장소는 한두 곳뿐이므로 클래스 계층 구조를 통해 함수를 메소드로 추가하는 데 시간이 걸리지 않습니다. 또는 단순하고 제한된 사용을 위해 일반 클래스, 테스트, 디자인 검토, 문서 또는 기본 클래스의 다른 속성을 추가하려는 실질적인 노력은 가치가 없습니다. 이 경우 직접 테스트를 수행하는 기능을 추가하는 것이 합리적입니다.

  2. 수업을 조정할 수 없습니다. 서브 클래 싱에 대해 생각하지만 할 수는 없습니다. 예를 들어, Java의 많은 클래스가 지정 final됩니다. 당신은 던지기를 시도 public class ExtendedSubTypeA extends SubTypeA {...} 하고 컴파일러는 확실하지 않은 용어로, 당신이하고있는 일이 불가능하다는 것을 알려줍니다. 죄송합니다, 객체 지향 모델의 우아함과 정교함! 누군가 유형을 확장 할 수 없다고 결정했습니다! 불행히도 많은 표준 라이브러리는 final이며 클래스를 만드는 final것은 일반적인 디자인 지침입니다. 기능 종료는 남은 것입니다.

    BTW, 이것은 정적으로 유형이 지정된 언어로 제한되지 않습니다. 동적 언어 파이썬에는 C로 구현 된 커버에서 실제로 수정할 수없는 많은 기본 클래스가 있습니다. Java와 마찬가지로 대부분의 표준 유형이 포함됩니다.

  3. 코드가 외부 코드입니다. 다양한 데이터베이스 서버, 미들웨어 엔진 및 제어하거나 조정할 수없는 기타 코드베이스에서 제공되는 클래스와 객체로 개발하고 있습니다. 코드는 다른 곳에서 생성 된 객체의 소비자가 적습니다. 하위 클래스를 만들 수 있다고해도 하위 클래스 SuperType에서 객체를 생성하는 데 의존하는 라이브러리를 얻을 수는 없습니다. 그들은 당신에게 당신의 변형이 아닌 그들이 알고있는 유형의 인스턴스를 당신에게 줄 것입니다. 항상 그런 것은 아닙니다 ... 때로는 유연성을 위해 만들어졌으며 사용자가 급지하는 클래스의 인스턴스를 동적으로 인스턴스화합니다. 또는 팩토리가 구성 할 서브 클래스를 등록하는 메커니즘을 제공합니다. XML 파서는 그러한 진입 점을 제공하는 데 특히 능숙 해 보인다. 예를 들어 또는 파이썬의 lxml . 그러나 대부분의 코드 기반은 이러한 확장을 제공 하지 않습니다 . 그들이 만들어 놓은 클래스를 당신에게 돌려 줄 것입니다. 순전히 객체 지향 유형 선택기를 사용할 수 있도록 결과를 사용자 정의 결과로 프록시하는 것은 일반적으로 의미가 없습니다. 유형 차별을하려는 경우 상대적으로 조잡하게 수행해야합니다. 그러면 타입 테스트 코드가 매우 적합 해 보입니다.

  4. 불쌍한 사람의 제네릭 / 여러 발송. 코드에 다양한 유형을 허용하고 매우 다양한 유형의 메소드를 갖는 것이 우아하지 않다고 생각합니다. public void add(Object x)논리적 보이지만 아닌 배열 addByte, addShort, addInt, addLong, addFloat, addDouble, addBoolean, addChar, 및 addString변형 (몇 가지 이름). 높은 수퍼 유형을 취한 다음 유형별로 수행 할 작업을 결정하는 기능 또는 방법을 가짐으로써 매년 Booch-Liskov Design Symposium에서 Purity Award를 수상하지는 않지만 헝가리 이름 은 더 간단한 API를 제공합니다. 어떤 의미에서, 당신의 is-a또는is-instance-of 테스트는 기본적으로 지원하지 않는 언어 컨텍스트에서 일반 또는 다중 디스패치를 ​​시뮬레이션합니다.

    제네릭오리 타이핑 모두에 대한 기본 제공 언어 지원은 "우아하고 적절한 것을 수행"할 가능성을 높여서 유형 검사의 필요성을 줄입니다. Julia 및 Go와 같은 언어로 표시 되는 다중 디스패치 / 인터페이스 선택은 직접 유형 테스트를 유형 기반 "무엇을 수행할지"에 대한 내장 메커니즘으로 대체합니다. 그러나 모든 언어가이를 지원하는 것은 아닙니다. 예를 들어 Java는 일반적으로 단일 배포이며 관용구는 오리 타이핑에 매우 친숙하지 않습니다.

    그러나 상속, 제네릭, 오리 타이핑 및 다중 디스패치와 같은 이러한 유형 구분 기능을 사용하더라도 개체 유형에 따라 무언가를 수행하는 단일 통합 루틴을 갖는 것이 때로는 편리합니다. 명확하고 즉시. 에서는 메타 프로그래밍 나는 그것이 근본적으로 피할 발견했다. 직접 유형 문의로 돌아가는 것이 "실용주의"또는 "더러운 코딩"을 구성하는지 여부는 설계 철학과 신념에 따라 다릅니다.


특정 유형에 대해 작업을 수행 할 수 없지만 암시 적으로 변환 할 수있는 서로 다른 두 가지 유형에 대해 작업을 수행 할 수있는 경우 두 변환에서 동일한 결과를 얻을 경우에만 오버로드가 적합합니다. 그렇지 않으면 하나는 .NET처럼 지저분한 행동으로 끝나는 (1.0).Equals(1.0f)진정한 항복 [인수하기로 촉진 double,하지만 (1.0f).Equals(1.0)거짓 항복 [인수하기에 촉진 object] 자바, Math.round(123456789*1.0)123456789를 얻을 수 있지만, Math.round(123456789*1.0)123,456,792은 [인수를 촉진 산출 float보다는 double].
supercat

이것이 자동 유형 캐스팅 / 에스컬레이션에 대한 고전적인 주장입니다. 적어도 우연한 경우에는 나쁘고 역설적 인 결과. 동의하지만 내 답변과 어떻게 관련이 있는지 잘 모르겠습니다.
Jonathan Eunice

다른 유형의 다른 이름의 메소드를 사용하는 대신 오버로드를 옹호하는 것처럼 보이는 귀하의 포인트 # 4에 응답했습니다.
supercat

2
@supercat 내 시력이 나쁘다고 비난하지만 두 표현은 Math.round나에게 동일합니다. 차이점이 뭐야?
Lily Chung

2
@IstvanChung이 : 어머 ... 후자는 있었어야 Math.round(123456789)[사람이 다시 쓰는 경우에 일어날 일을 나타내는 Math.round(thing.getPosition() * COUNTS_PER_MIL)그 실현하지, 정해지지 않은 위치 값을 반환 getPosition반환 int또는 long.]
supercat

25

필자가 필요로 한 주요 상황 equals(other)은 정확한 유형에 따라 다른 알고리즘이 필요할 수 있는 메소드 와 같이 두 개의 객체를 비교할 때였습니다 other. 그럼에도 불구하고, 그것은 매우 드 rare니다.

내가 드린 또 다른 상황은 매우 드물게 역 직렬화 또는 파싱 후, ​​때로는 더 구체적인 유형으로 안전하게 캐스팅 해야하는 상황입니다.

또한 때로는 제어하지 않는 타사 코드를 해결하기 위해 해킹이 필요합니다. 그것은 당신이 정말로 정기적으로 사용하고 싶지 않은 것들 중 하나이지만, 정말로 필요할 때 거기에 기뻐합니다.


1
나는 역 직렬화 사례에 순간적으로 기뻐했으며, 그곳에서 그것을 사용하는 것을 잘못 기억했다. 그러나 이제는 내가 어떻게 될지 상상할 수 없습니다! 나는 그에 대한 이상한 유형 조회를했음을 안다. 그러나 이것이 유형 테스트 를 구성하는지 여부는 확실하지 않습니다 . 직렬화는 더 밀접한 병렬 작업 일 수 있습니다. 구체적 유형에 대해 개체를 조사해야합니다.
svidgen

1
직렬화는 일반적으로 다형성을 사용하여 수행 할 수 있습니다. 역 직렬화 할 때는 BaseClass base = deserialize(input)아직 유형을 모르기 때문에 와 같은 작업을 수행하는 경우가 많습니다. 그런 다음 해당 유형을 if (base instanceof Derived) derived = (Derived)base정확한 파생 유형으로 저장해야합니다.
Karl Bielefeldt

1
평등은 말이됩니다. 필자의 경험에 따르면 이러한 방법은 종종 "이 두 객체의 콘크리트 유형이 동일한 경우 모든 필드가 동일한 지 여부를 반환합니다. 그렇지 않으면, 거짓 (또는 비교할 수없는)을 반환하십시오”.
Jon Purdy

2
특히, 자신이 테스트 유형을 발견한다는 사실은 다형성 평등 비교가 독사의 둥지라는 좋은 지표입니다.
Steve Jessop

12

다음과 같은 상황에서 표준 (그러나 드문 경우) 사례는 다음과 같습니다.

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    DoSomethingA((SubTypeA) o )
  } else {
    DoSomethingB((SubTypeB) o );
  }
}

기능 DoSomethingA이상이 DoSomethingB쉽게의 상속 트리의 멤버 함수로 구현 될 수 없다 SuperType/의 SubTypeA/ SubTypeB. 예를 들어

  • 하위 유형은 변경할 수없는 라이브러리의 일부이거나
  • DoSomethingXXX해당 라이브러리에 코드를 추가하면 금지 된 종속성이 발생합니다.

이 문제를 피할 수있는 상황이 종종 있습니다 (예 : SubTypeAand SubTypeB에 대한 랩퍼 또는 어댑터를 작성하거나 DoSomething의 기본 조작의 관점에서 completly 를 다시 구현하려는 SuperType경우). 이러한 솔루션이 번거 롭거나 가치가없는 경우가 있습니다. 명시 적 유형 테스트를 수행하는 것보다 더 복잡하고 확장 성이 낮은 것들.

어제 일한 예 : 나는 객체 목록 처리를 병렬화하려고하는 상황이있었습니다 (유형 SuperType은 정확히 두 개의 다른 하위 유형으로, 더 많을 가능성은 거의 없습니다). 병렬화되지 않은 버전에는 두 개의 루프가 포함되어 있습니다. 하나는 하위 유형 A의 객체에 대한 루프이고 다른 하나 DoSomethingA는 하위 유형 B의 객체에 대한 루프입니다 DoSomethingB.

"DoSomethingA"및 "DoSomethingB"메소드는 서브 타입 A 및 B의 범위에서 사용할 수없는 컨텍스트 정보를 사용하여 시간 집약적 인 계산입니다 (따라서 서브 타입의 멤버 함수로 구현하는 것은 의미가 없습니다). 새로운 "병렬 루프"의 관점에서, 그것들을 균일하게 처리함으로써 일을 훨씬 쉽게 해주므로 DoSomethingTo위에서 와 비슷한 기능을 구현했습니다 . 그러나 "DoSomethingA"와 "DoSomethingB"의 구현을 살펴보면 내부적으로 매우 다르게 작동한다는 것을 알 수 있습니다. 따라서 SuperType많은 추상 메소드로 확장하여 일반적인 "DoSomething"을 구현하려고 시도하는 것은 실제로 작동하지 않거나 완전히 과도하게 디자인하는 것을 의미합니다.


2
이 시나리오가 고안되지 않은 안개가 자욱한 뇌를 확신시키기 위해 작고 구체적인 예를 추가 할 수 있습니까?
svidgen

명확하게하기 위해, 나는 그것이 말하는 게 아니에요 이다 인위적인. 오늘은 안개가 낀 것 같아요.
svidgen

@ svidgen : 이것은 벌어지고있는 것과는 거리가 멀다. 실제로 오늘 나는 이러한 상황에 직면했다 (비지니스 내부를 포함하기 때문에이 예제를 게시 할 수는 없지만) 그리고 "is"연산자를 사용하는 것은 예외이며 드문 경우에만 수행되어야한다는 다른 답변에 동의합니다.
Doc Brown

내가 간과 한 편집 내용이 좋은 예라고 생각합니다. ... 제어 할 때 와 서브 클래스 때도 괜찮은 경우가 SuperType있습니까?
svidgen

6
구체적인 예를 들면 다음과 같습니다. 웹 서비스의 JSON 응답에서 최상위 컨테이너는 사전 또는 배열 일 수 있습니다. 일반적으로 (예를 들어 실제 객체에 JSON을집니다 어떤 도구가 NSJSONSerialization(예를 Obj-C)에,하지만 당신은 그것을 사용하기 전에 당신이 그것을 확인 그래서 단순히 응답이 예상 유형을 포함 신뢰하지 않는다 if ([theResponse isKindOfClass:[NSArray class]])...) .
Caleb

5

밥 아저씨는 다음과 같이 말합니다.

When your compiler forgets about the type.

Clean Coder 에피소드 중 하나에서 Employees 를 반환하는 데 사용되는 함수 호출의 예를 제공했습니다 . Manager의 하위 유형입니다 Employee. 우리가 Managerid 를 받아들이고 그를 사무실로 소환 하는 응용 프로그램 서비스가 있다고 가정 해 봅시다 :) 함수 getEmployeeById()는 super-type을 반환 Employee하지만이 유스 케이스에서 관리자가 반환되는지 확인하고 싶습니다.

예를 들면 다음과 같습니다.

var manager = employeeRepository.getEmployeeById(empId);
if (!(manager is Manager))
   throw new Exception("Invalid Id specified.");
manager.summon();

여기에서 쿼리로 반환 된 직원이 실제로 관리자인지 확인하고 있습니다 (즉, 관리자 일 것으로 예상하고 그렇지 않으면 빠르게 실패 할 경우).

가장 좋은 예는 아니지만 결국 밥 아저씨입니다.

최신 정보

메모리에서 기억할 수있는만큼 예제를 업데이트했습니다.


1
이 예제에서 Manager의 구현으로 summon()예외가 발생 하지 않는 이유는 무엇 입니까?
svidgen

@svidgen은 아마도 s CEO를 소환 할 수 있습니다 Manager.
user253751

@svidgen, 그렇다면 employeeRepository.getEmployeeById (empId)가 관리자를 반환 할 것이 확실하지 않습니다
Ian

@Ian 나는 그것을 문제로 보지 않는다. 호출 코드가을 요청하는 경우 Employee,처럼 동작하는 것을 얻는 것만주의해야합니다 Employee. 하위 클래스의 Employee권한, 책임 등이 서로 다른 경우 실제 권한 시스템보다 유형 테스트가 더 나은 옵션은 무엇입니까?
svidgen

3

타입 확인은 언제입니까?

못.

  1. 다른 기능의 유형과 관련된 행동을함으로써, 당신은 위반하고 개방 - 폐쇄 원칙을 사용자가 변경하여 유형의 기존 동작을 수정하는 무료이기 때문에, is일부 언어에서 절 또는을 (또는에 따라 시나리오) is검사를 수행하는 함수의 내부를 수정하지 않고 유형을 확장 할 수 없기 때문 입니다.
  2. 더 중요한 것은 is수표가 Liskov 대체 원칙을 위반한다는 강력한 신호입니다 . 작동하는 모든 것은 하위 유형이 무엇인지 완전히 무시SuperType 해야 합니다.
  3. 당신이있어 암시 적 유형의 이름으로 어떤 행동을 연결. 이러한 암묵적 계약은 코드 전체에 분산되어 있으며 실제 클래스 멤버처럼 보편적이고 일관되게 적용되지 않기 때문에 코드를 유지하기가 더 어려워집니다.

그러나 is수표는 다른 대안 보다 나쁘지 않을 수 있습니다 . 모든 공통 기능을 기본 클래스에 넣는 것은 어려운 일이며 종종 더 나쁜 문제로 이어집니다. 인스턴스가 어떤 "유형"인지에 대한 플래그 또는 열거가있는 단일 클래스를 사용하는 것은 유형 시스템 우회를 모든 소비자 에게 퍼 뜨리기 때문에 끔찍한 것보다 나쁩니다 .

간단히 말해서, 항상 유형 검사를 강력한 코드 냄새로 간주해야합니다. 그러나 모든 가이드 라인과 마찬가지로, 어떤 가이드 라인 위반이 가장 공격적이지 않은지를 선택해야하는 경우가 있습니다.


3
지원하지 않는 언어로 대수 데이터 유형구현하는 경우 한 가지 사소한 경고가 있습니다. 그러나 상속과 타입 체크 (typechecking)의 사용은 순전히 해킹 된 구현이다. 의도는 하위 유형을 도입하는 것이 아니라 값을 분류하는 것입니다. 나는 ADT가 유용하고 결코 강력한 예선자가 아니기 때문에 그것을 제기 하지만, 그렇지 않으면 나는 완전히 동의한다. instanceof구현 세부 정보가 유출되고 추상화가 중단됩니다.
Doval

17
"Never"는 특히 그런 상황에서 마음에 들지 않는 단어입니다.
Doc Brown

10
"never"가 실제로 "때때로"를 의미한다면, 당신이 옳습니다.
Caleb

2
OK 는 허용 가능하고 수용 가능하지만 반드시 이상적이거나 최적 인 것은 아닙니다. OK아닌 것과 대조 : 무언가가 좋지 않으면 전혀 해서는 안됩니다. 당신이 나타내는 바와 같이, 무언가의 유형을 확인 할 필요는 코드의 깊은 문제의 표시 일 수 있지만, 가장 편리한, 적어도 나쁜 옵션, 그리고 이러한 상황에서 분명의 경우가 있습니다 확인 에 도구를 사용하려면 처분. (모든 상황에서 피하는 것이 쉬운 경우에는 처음에는 없을 것입니다.) 문제는 이러한 상황을 파악하는 데 실패하고 결코 도움이되지 않습니다.
Caleb

2
@supercat : 메소드는 요구 사항을 충족하는 가능한 최소형을 요구해야 합니다 . IEnumerable<T>"마지막"요소가 존재한다고 약속하지 않습니다. 분석법에 그러한 요소가 필요한 경우, 해당 요소의 존재를 보장하는 유형이 필요합니다. 그리고 해당 유형의 하위 유형은 "마지막"방법의 효율적인 구현을 제공 할 수 있습니다.
cHao

2

큰 코드 기반 (100K 줄 이상의 코드)이 있고 배송에 가까워 지거나 나중에 병합해야하는 지점에서 작업하는 경우 많은 비용을 들이지 않고 많은 비용을 부담해야합니다.

그런 다음 때때로 시스템의 큰 굴절 기 또는 일부 현지화 된“유형 테스트”옵션이 있습니다. 이로 인해 기술 부채가 생겨 최대한 빨리 상환되어야하지만 종종 그렇지 않습니다.

(예를 들어 사용하기에 충분히 작은 코드는 더 나은 디자인을 명확하게 볼 수있을 정도로 작기 때문에 예를 제시하는 것은 불가능합니다.)

즉, 목표가 디자인의 청결성을 위해“투표”를하기보다는 임금을 지불 하는 것입니다.


다른 일반적인 경우는 UI 코드입니다. 예를 들어 일부 직원 유형에 대해 다른 UI를 표시하지만 UI 개념이 모든 "도메인"클래스로 이스케이프되는 것을 원하지는 않습니다.

"유형 테스트"를 사용하여 표시 할 UI 버전을 결정하거나 "도메인 클래스"에서 "UI 클래스"로 변환하는 멋진 조회 테이블을 가질 수 있습니다. 조회 테이블은 한 곳에서 "유형 테스트"를 숨기는 방법입니다.

데이터베이스 업데이트 코드에는 UI 코드와 동일한 문제가있을 수 있지만 데이터베이스 업데이트 코드는 하나만있는 경향이 있지만 표시되는 객체 유형에 맞게 조정해야하는 다양한 화면이있을 수 있습니다.


방문자 패턴은 종종 UI 사례를 해결하는 좋은 방법입니다.
Ian Goldby

@IanGoldby는 때때로 동의 할 수 있지만 여전히 "유형 테스트"를하고 있습니다.
Ian

일반 가상 메소드를 호출 할 때 숨겨져있는 것과 같은 의미로 숨겨져 있습니까? 아니면 다른 의미가 있습니까? 내가 사용한 방문자 패턴에는 유형에 따른 조건문이 없습니다. 그것은 모두 언어에 의해 이루어집니다.
Ian Goldby

@IanGoldby, WPF 또는 WinForms 코드를 이해하기 어렵게 만들 수 있다는 의미에서 숨겨져있었습니다. 일부 웹 기반 UI의 경우 매우 잘 작동 할 것으로 기대합니다.
Ian

2

LINQ 구현은 가능한 성능 최적화를 위해 많은 유형 검사를 사용한 다음 IEnumerable에 대한 대체를 사용합니다.

가장 명백한 예는 아마도 ElementAt 메서드 (.NET 4.5 소스의 작은 발췌) 일 것입니다.

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index) { 
    IList<TSource> list = source as IList<TSource>;

    if (list != null) return list[index];
    // ... and then an enumerator is created and MoveNext is called index times

그러나 Enumerable 클래스에는 비슷한 패턴이 사용되는 곳이 많이 있습니다.

따라서 일반적으로 사용되는 하위 유형의 성능을 최적화하는 것이 유효합니다. 이것이 어떻게 더 잘 설계되었는지 잘 모르겠습니다.


그것은 인터페이스는 기본 구현을 제공 할 수있는 편리한 수단을 제공하고함으로써 더 나은 설계되었습니다 수는 IEnumerable<T>에서와 같은 많은 방법을 포함 List<T>과 함께, Features, 방법이 아니라, 느리게, 또는 전혀 일을 예상 할 수 있는지를 나타내는 속성을 뿐만 아니라 소비자가 컬렉션에 대해 안전하게 결정할 수있는 다양한 가정 (예 : 크기 및 / 또는 기존 컨텐츠는 절대 변경되지 않음을 보증합니다 (유형은 지원할 수 Add있지만 기존 컨텐츠는 불변임을 보증 함)).
supercat

유형이 서로 다른 시간에 완전히 다른 두 가지 중 하나를 보유해야하고 요구 사항이 상호 배타적이므로 별도의 필드를 사용하는 것이 중복되는 시나리오를 제외하고는 일반적으로 try-casting이 필요합니다. 기본 인터페이스의 일부였던 멤버는 그렇지 않았습니다. 즉, 일부 멤버를 포함해야하지만 기본 인터페이스의 누락을 해결하기 위해 try-casting을 사용해서는 안되는 인터페이스를 사용하는 코드는 아니지만 기본 인터페이스 작성은 클라이언트의 try-cast 요구를 최소화해야합니다.
supercat

1

게임 개발에서, 특히 충돌 감지에서 종종 발생하는 예가 있는데, 어떤 유형의 유형 테스트를 사용하지 않으면 다루기가 어렵습니다.

모든 게임 오브젝트가 공통 기본 클래스에서 파생되었다고 가정합니다 GameObject. 각 오브젝트는 강체 충돌 형상 가지고 CollisionShape있지만, 실제 충돌 형상이 모두 같은 구체적인 서브 것 (검색어 위치, 방향 등 말) 공통의 인터페이스를 제공 할 수있다 Sphere, Box, ConvexHull등의 기하학적 객체의 타입에 고유 정보를 기억 ( 실제 예는 여기 를 참조 하십시오 )

이제 충돌을 테스트하려면 각 충돌 모양 유형 쌍에 대한 함수를 작성해야합니다.

detectCollision(Sphere, Sphere)
detectCollision(Sphere, Box)
detectCollision(Sphere, ConvexHull)
detectCollision(Box, ConvexHull)
...

두 기하학적 유형의 교차를 수행하는 데 필요한 특정 수학이 포함되어 있습니다.

게임 루프의 각 '틱'에서 충돌이 있는지 객체 쌍을 확인해야합니다. 그러나 나는 GameObjects 및 해당하는 CollisionShapes 에만 액세스 할 수 있습니다 . 분명히 어떤 충돌 감지 기능을 호출해야하는지 구체적 유형을 알아야합니다. 이중 디스패치 조차도 (어쨌든 유형을 확인하는 것과 논리적으로 다르지 않습니다) 여기에서 도움이 될 수 있습니다 *.

실제로이 상황에서 내가 본 물리 엔진 (Bullet and Havok)은 한 가지 형식의 형식 테스트에 의존합니다.

나는 이것이 반드시 좋은 해결책 이라고 말하지는 않습니다. 이 문제에 대한 가능한 적은 수의 해결책 중 최고 일 수 있습니다.

* 기술적으로 이다 (N은 당신이 가진 모양 유형의 개수) N (N + 1) / 2 조합을 필요로 만 정말 무슨 일을하는지 당황 것 끔찍한 복잡한 방법으로 이중 파견을 사용할 수있는 두 모양의 유형을 동시에 찾는 것이므로 현실적인 솔루션이라고 생각하지 않습니다.


1

때로는 특정 작업을 수행 할 책임이 없기 때문에 모든 클래스에 공통 메소드를 추가하지 않으려는 경우가 있습니다.

예를 들어 일부 도면 요소를 그리려고하지만 도면 코드를 직접 추가하지는 않습니다. 다중 디스패치를 ​​지원하지 않는 언어에서는 다음 코드로 끝날 수 있습니다.

void DrawEntity(Entity entity) {
    if (entity instanceof Circle) {
        DrawCircle((Circle) entity));
    else if (entity instanceof Rectangle) {
        DrawRectangle((Rectangle) entity));
    } ...
}

이 코드가 여러 곳에 나타날 때 문제가되며 새 엔티티 유형을 추가 할 때 어디서나 수정해야합니다. 이 경우 방문자 패턴을 사용하여 피할 수 있지만 때로는 지나치게 엔지니어링하지 말고 단순하게 유지하는 것이 좋습니다. 타입 테스트가 정상인 상황입니다.


0

내가 사용하는 유일한 시간은 반사와 함께입니다. 그러나 그렇다하더라도 (또는 단지와 같은 특별한 클래스에 하드 코딩 대부분이 아닌 특정 클래스에 하드 코딩 된 동적 검사 인 String또는 List).

동적 검사는 다음을 의미합니다.

boolean checkType(Type type, Object object) {
    if (object.isOfType(type)) {

    }
}

하드 코딩되지 않은

boolean checkIsManaer(Object object) {
    if (object instanceof Manager) {

    }
}

0

형식 테스트와 형식 캐스팅은 매우 밀접한 관련이있는 개념입니다. 그래서 밀접하게 당신이해야한다는 확신 느낌 관련 결코 타입 테스트를하지 않는 한 의도는 결과에 따라 객체를 캐스팅 입력하는 것입니다.

이상적인 객체 지향 디자인에 대해 생각할 때 형식 테스트 (및 캐스팅) 절대 일어나지 않아야합니다 . 그러나 이제는 객체 지향 프로그래밍 이 이상적이지 않다는 것을 알았습니다 . 때때로, 특히 하위 레벨 코드의 경우 코드가 이상적으로 사실로 유지 될 수 없습니다. Java에서 ArrayLists의 경우입니다. 런타임에 어떤 클래스가 배열에 저장되는지 알지 못하므로 배열을 만들고 Object[]정적으로 올바른 유형으로 캐스트합니다.

타입 테스트 (및 타입 캐스팅)에 대한 일반적인 요구 Equals는 대부분의 언어에서 평범한 것으로 여겨지 는 메소드 에서 나온다는 점이 지적되었습니다 Object. 구현시 두 객체가 동일한 유형인지 확인하기 위해 세부 사항을 확인해야하며, 어떤 유형인지 테스트해야합니다.

형식 테스트도 자주 반영됩니다. 종종 반환하는 메소드 Object[]나 다른 일반 배열 을 가지게되며 , Foo어떤 이유로 든 모든 객체를 꺼내려고 합니다. 이것은 유형 테스트 및 캐스팅을 완벽하게 합법적으로 사용하는 것입니다.

일반적으로 형식 테스트는 코드를 불필요하게 특정 구현이 작성된 방식에 결합 할 때 좋지 않습니다 . 따라서 선, 사각형 및 원의 교차점을 찾고자하는 경우와 같이 각 유형 또는 유형의 조합에 대해 특정 테스트를 쉽게 수행 할 수 있으며 교차 함수에는 각 조합마다 다른 알고리즘이 있습니다. 목표는 한 종류의 객체에 대한 세부 정보를 해당 객체와 같은 곳에 배치하는 것입니다. 코드를보다 쉽게 ​​유지 관리하고 확장 할 수 있기 때문입니다.


1
ArrayListsJava에는 제네릭이 없기 때문에 런타임에 저장되는 클래스를 알지 못하고 Oracle이 마침내 도입되었을 때 Oracle은 제네릭리스 코드와의 하위 호환성을 선택했습니다. equals같은 문제가 있으며 어쨌든 의심스러운 디자인 결정입니다. 평등 비교는 모든 유형에 적합하지 않습니다.
Doval

1
엄밀히 말하면 Java 컬렉션은 내용을 아무 것도 캐스팅 하지 않습니다 . 컴파일러는 각 액세스 위치에 타입 캐스트를 주입합니다. 예 :String x = (String) myListOfStrings.get(0)

마지막으로 Java 소스 (1.6 또는 1.5 일 수 있음)를 살펴보면 ArrayList 소스에 명시 적 캐스트가있었습니다. 적절한 이유로 (압축 된) 컴파일러 경고를 생성하지만 어쨌든 허용됩니다. 제네릭이 구현되는 방식 때문에 항상 Object액세스 할 때까지는 항상 가능하다고 가정 합니다. Java의 제네릭은 컴파일러 규칙에 의해 안전한 내재 된 캐스팅 만 제공합니다.
meustrus

0

두 가지 유형 이 포함 된 결정을 내려야하며이 결정이 해당 유형 계층 구조 외부의 개체에 캡슐화 된 경우 허용됩니다 . 예를 들어 처리 대기중인 객체 목록에서 다음에 처리 할 객체를 예약한다고 가정 해 보겠습니다.

abstract class Vehicle
{
    abstract void Process();
}

class Car : Vehicle { ... }
class Boat : Vehicle { ... }
class Truck : Vehicle { ... }

이제 우리의 비즈니스 로직이 문자 그대로 "모든 자동차가 보트와 트럭보다 우선합니다"라고 가정 해 봅시다. Priority클래스에 속성을 추가하면 이 비즈니스 논리를 명확하게 표현할 수 없습니다.

abstract class Vehicle
{
    abstract void Process();
    abstract int Priority { get }
}

class Car : Vehicle { public Priority { get { return 1; } } ... }
class Boat : Vehicle { public Priority { get { return 2; } } ... }
class Truck : Vehicle { public Priority { get { return 2; } } ... }

문제는 우선 순위 순서를 이해하기 위해서는 모든 서브 클래스를 봐야합니다. 즉, 서브 클래스에 커플 링을 추가해야한다는 것입니다.

물론 우선 순위를 상수로 정하고 스스로 스스로 수업에 배치해야 비즈니스 로직 일정을 계속 유지할 수 있습니다.

static class Priorities
{
    public const int CAR_PRIORITY = 1;
    public const int BOAT_PRIORITY = 2;
    public const int TRUCK_PRIORITY = 2;
}

그러나 실제로 스케줄링 알고리즘은 미래에 변경 될 수 있으며 결국 유형 이상의 것에 만 의존 할 수 있습니다. 예를 들어 "무게가 5000kg 이상인 트럭은 다른 모든 차량보다 우선 순위가 높습니다"라고 말할 수 있습니다. 그렇기 때문에 스케줄링 알고리즘이 자체 클래스에 속하는 이유는 어떤 유형을 먼저 검사해야하는지 결정 하는 것이 좋습니다 .

class VehicleScheduler : IScheduleVehicles
{
    public Vehicle WhichVehicleGoesFirst(Vehicle vehicle1, Vehicle vehicle2)
    {
        if(vehicle1 is Car) return vehicle1;
        if(vehicle2 is Car) return vehicle2;
        return vehicle1;
    }
}

이는 비즈니스 로직을 구현하는 가장 간단한 방법이며 향후 변경에 가장 유연합니다.


귀하의 특정 예에 동의하지 않지만 다른 의미를 가진 다른 유형을 보유 할 수있는 개인 참조 원칙에 동의합니다. 더 나은 예제로 제안하는 것은 null, a String또는 a를 보유 할 수있는 필드입니다 String[]. 객체의 99 %가 정확히 하나의 문자열을 필요로하는 경우, 별도로 구성된 모든 문자열을 캡슐화 String[]하면 상당한 스토리지 오버 헤드가 추가 될 수 있습니다. 에 대한 직접 참조를 사용하여 단일 문자열 케이스를 처리하면 String더 많은 코드가 필요하지만 저장 공간이 절약되고 작업 속도가 빨라질 수 있습니다.
supercat

0

형식 테스트는 도구이므로 현명하게 사용하면 강력한 동맹이 될 수 있습니다. 잘못 사용하면 코드 냄새가 나기 시작합니다.

소프트웨어에서 요청에 대한 응답으로 네트워크를 통해 메시지를 받았습니다. 직렬화 해제 된 모든 메시지는 공통 기본 클래스를 공유했습니다 Message.

클래스 자체는 매우 간단했습니다. 유형 C # 속성으로 페이로드와 마샬링 및 마샬링 해제 루틴 (실제로 메시지 형식의 XML 설명에서 t4 템플릿을 사용하여 대부분의 클래스를 생성했습니다)

코드는 다음과 같습니다.

Message response = await PerformSomeRequest(requestParameter);

// Server (out of our control) would send one message as response, but 
// the actual message type is not known ahead of time (it depended on 
// the exact request and the state of the server etc.)
if (response is ErrorMessage)
{ 
    // Extract error message and pass along (for example using exceptions)
}
else if (response is StuffHappenedMessage)
{
    // Extract results
}
else if (response is AnotherThingHappenedMessage)
{
    // Extract another type of result
}
// Half a dozen other possible checks for messages

물론 메시지 아키텍처가 더 잘 설계 될 수 있다고 주장 할 수는 있지만 C # 용이 아닌 오래 전에 설계되었으므로 이것이 그 자체입니다. 여기서 타입 테스트는 너무 초라하지 않은 방식으로 우리에게 실제 문제를 해결했습니다.

주목할만한 점은 C # 7.0이 패턴 일치를 얻고 있다는 것 입니다 (많은면에서 스테로이드에 대한 유형 테스트입니다). 모두 나쁘지는 않습니다 ...


0

일반적인 JSON 파서를 가져옵니다. 성공적인 구문 분석 결과는 배열, 사전, 문자열, 숫자, 부울 또는 널값입니다. 그중 하나 일 수 있습니다. 그리고 배열의 요소 또는 사전의 값은 이러한 유형 중 하나 일 수 있습니다. 데이터가 프로그램 외부에서 제공되기 때문에, 당신은 어떤 결과를 받아 들여야한다 (즉, 당신이 사고없이 그것을 받아 들일 필요가있다, 당신은 할 수 있습니다 당신이 무엇을 기대하지 그 결과를 거부).


글쎄, 일부 JSON deserializer는 구조에 "추론 필드"에 대한 유형 정보가있는 경우 객체 트리를 인스턴스화하려고 시도합니다. 하지만 그래 나는 이것이 Karl B의 대답이 향한 방향이라고 생각한다.
svidgen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.