하위 클래스와 하위 유형의 차이점은 무엇입니까?


44

Liskov 대체 원리에 대한 이 질문 에 대한 최고 등급의 답변 은 하위 유형하위 클래스 라는 용어를 구별하기 위해 고통을받습니다 . 또한 일부 언어는 두 언어를 혼동하는 반면 다른 언어는 그렇지 않다는 점을 지적합니다.

내가 가장 친숙한 객체 지향 언어 (Python, C ++)에서 "유형"과 "클래스"는 동의어 개념입니다. C ++의 관점에서 하위 유형과 하위 클래스를 구별한다는 것은 무엇을 의미합니까? 예를 들어,이 Foo클래스는 하위 클래스이지만 하위 유형은 아닙니다 FooBase. 경우 foo의 인스턴스 Foo, 것이 라인 :

FooBase* fbPoint = &foo;

더 이상 유효하지 않습니까?


6
실제로 파이썬에서 "type"과 "class"는 별개의 개념입니다. 사실, 파이썬은 동적으로 입력되고, "유형"는 개념이 아니다 전혀 파이썬한다. 불행히도, 파이썬 개발자는 그것을 이해하지 못하고 여전히 둘을 혼란시킵니다.
Jörg W Mittag

11
"Type"과 "class"는 C ++에서도 구별됩니다. "int의 배열"은 유형입니다. 어떤 수업입니까? "int 유형의 변수에 대한 포인터"는 유형입니다. 어떤 수업입니까? 이것들은 어떤 클래스도 아니지만 분명히 유형입니다.
Eric Lippert

2
나는 그 질문과 그 대답을 읽은 후에 바로 이것이 궁금합니다.
user369450

4
@JorgWMittag 파이썬에 "유형"이라는 개념이 없다면, 누군가 문서를 쓴 사람에게 말해야한다 : docs.python.org/3/library/stdtypes.html
Matt

@Matt는 공정한 것으로 3.5 형으로 만들어졌으며, 특히 최근에 사용이 허용 된 생산 표준에 따라 매우 최근에 만들어졌습니다.
Jared Smith

답변:


53

서브 타이핑 은 서브 타입이 대체 가능성이라는 개념에 의해 다른 데이터 타입 (슈퍼 타입)과 관련된 데이터 타입 인 형태 다형성의 한 형태입니다. 즉, 슈퍼 타입의 요소에서 작동하도록 작성된 프로그램 요소 (일반적으로 서브 루틴 또는 함수)는 하위 유형의 요소에서 작동합니다.

S의 하위 유형 인 경우 하위 유형 T관계가 종종 쓰여 지므로 유형 유형 이 예상되는 컨텍스트에서 S <: T모든 유형의 용어를 S안전하게 사용할 수 있습니다 T. 서브 타이핑의 정확한 의미는 결정적으로 주어진 프로그래밍 언어에서 "어느 상황에서 안전하게 사용되는지"의 세부 사항에 달려 있습니다.

서브 클래 싱을 서브 타이핑과 혼동해서는 안됩니다. 일반적으로 서브 타이핑은 is-a 관계를 설정하는 반면, 서브 클래 싱은 구현 만 재사용하고 의미 론적 관계 일 필요는 없으며 구문 상 관계를 설정합니다 (상속이 행동 서브 타이핑을 보장하지는 않습니다).

이러한 개념을 구별하기 위해 서브 타이핑은 인터페이스 상속 이라고도하며 서브 클래 싱은 구현 상속 또는 코드 상속이라고합니다.

참고
하위 유형
상속


1
아주 잘 말했다. 질문의 맥락에서 C ++ 프로그래머는 종종 유형 체계와의 서브 타이핑 관계를 전달하기 위해 순수한 가상 기본 클래스를 사용한다는 점을 언급 할 가치가 있습니다. 물론 일반적인 프로그래밍 방식이 선호되는 경우가 많습니다.
Aluan Haddad

6
"subtyping의 정확한 의미는 결정적으로 주어진 프로그래밍 언어에서"어느 상황에서 안전하게 사용되는지 "의 세부 사항에 달려 있습니다. … LSP는 "안전한"의 의미에 대한 다소 합리적인 아이디어를 정의하고, 이러한 특정 형태의 "안전성"을 가능하게하기 위해 특정 조건이 충족해야하는 제약을 알려줍니다.
Jörg W Mittag

더미에 대한 또 하나의 샘플 : C ++에서 올바르게 이해하면 public상속에는 하위 유형이 private도입 되고 상속에는 하위 클래스가 도입됩니다.
Quentin

@Quentin public 상속은 하위 유형과 하위 클래스이지만 private이지만 하위 유형이지만 하위 유형은 아닙니다. Java 인터페이스와 같은 구조를 사용하여 서브 클래 싱없이 서브 타이핑을 할 수 있습니다
eques

27

유형은 , 우리가 여기에 대해 얘기하는 상황에서, 본질적으로 행동 보증의 집합입니다. 계약 , 만약 당신이 것입니다. 또는 프로토콜 인 스몰 토크에서 빌리는 용어 .

클래스는 방법의 번들입니다. 일련의 동작 구현 입니다.

서브 타이핑 은 프로토콜을 구체화하는 수단입니다. 서브 클래 싱 은 동작의 차이 만 설명함으로써 코드를 재사용하는 차동 코드 재사용의 수단입니다.

Java 또는 C♯를 사용한 경우 모든 유형이 유형이어야한다는 조언이 나올 수 interface있습니다. 당신이 윌리엄 쿡의를 읽으면 사실, 데이터 추상화, 재 방문을 이해 , 당신은 순서대로 그 언어 OO을 수행하는 것을 알 수 있습니다 당신은 해야 에만 사용 interface의 유형 등을. (재미있는 사실 : Java interface는 Objective-C의 프로토콜을 직접 사용하여 Smalltalk에서 직접 가져 왔습니다.)

이제, 우리는 논리적 결론에 조언을 코딩 및 자바의 버전 상상 것을 따르는 경우 에만 interface 다음의이 종류를, 그리고 클래스와 프리미티브가없는 한 interface다른 상속 한 반면,하는 하위 유형 관계를 만들 것입니다 class다른 뜻에서 상속 를 통한 차등 코드 재사용을위한 것입니다 super.

내가 아는 한, 상속 코드 (구현 상속 / 서브 클래 싱)와 계약 상속 ( 서브 타이핑)을 엄격하게 구분하는 주류 정적 유형 언어는 없습니다 . Java 및 C♯에서 인터페이스 상속은 순수한 서브 타이핑 (적어도 Java 8에 기본 메소드가 도입 될 때까지 그리고 C♯8도 가능할 때까지)이지만 클래스 상속 구현 상속뿐만 아니라 서브 타이핑입니다. 실험적으로 정적으로 유형이 지정된 객체 지향 LISP 방언에 대해 읽은 것을 기억합니다. 믹스 인 ( 동작 포함 ), 구조체 (상태 포함), 인터페이스 ( 설명)동작) 및 클래스 (하나 이상의 믹스 인으로 0 개 이상의 구조체를 구성하고 하나 이상의 인터페이스를 준수 함). 클래스 만 인스턴스화 할 수 있으며 인터페이스 만 유형으로 사용할 수 있습니다.

Python, Ruby, ECMAScript 또는 Smalltalk와 같이 동적으로 유형이 지정된 OO 언어에서는 일반적으로 객체의 유형을 일치하는 프로토콜 세트로 생각합니다. 복수 참고 : 객체가 여러 유형을 가질 수 있고, 난 그냥 유형의 각 개체가 있다는 사실에 대해 이야기하고 있지 않다 String또한 형식의 개체입니다 Object. (BTW : 클래스 이름을 사용하여 유형에 대해 이야기하는 방법에 유의하십시오. 얼마나 어리 석습니까!) 개체는 여러 프로토콜을 구현할 수 있습니다. 예를 들어, Ruby에서는 Arrays추가 할 수 있고 색인을 생성하고 반복하고 비교할 수 있습니다. 그것들은 그들이 구현하는 네 가지 프로토콜입니다!

이제 Ruby에는 유형이 없습니다. 그러나 루비 커뮤니티 에는 유형이 있습니다! 그러나 프로그래머의 헤드에만 존재합니다. 그리고 문서에서. 예를 들어, each요소를 하나씩 생성하여 호출 된 메서드에 응답하는 개체 는 열거 가능한 개체로 간주됩니다 . 그리고 불리는 믹스 인이 의존 이 프로토콜에. 개체가 올바른 경우 그래서, (단지 프로그래머의 머리에 존재), 다음은 (상속)을 믹스 할 수있다 믹스 인, 그리고 물론, 무료와 같은 시원한 방법의 모든 종류를 얻을 , , 등 의 위에.EnumerableEnumerablemapreducefilter

객체가 응답하는 경우 마찬가지로 <=>, 다음이 구현하는 것으로 간주됩니다 비슷한 프로토콜을, 그리고 그것은에 혼합 할 수 있습니다 Comparable믹스 인과 같은 물건을 얻을 <, <=, >, <=, ==, between?, 및 clamp무료. 그러나 모든 메소드 자체를 구현할 수도 있고 전혀 상속 Comparable할 수 없으며 여전히 비교 가능한 것으로 간주됩니다 .

좋은 예는 StringIO라이브러리로, 본질적 으로 문자열로 I / O 스트림을 가짜로 만듭니다. IO클래스 와 동일한 메소드를 모두 구현 하지만 둘 사이에 상속 관계는 없습니다. 그럼에도 불구하고, StringIO어느 곳에서나 사용할 IO수 있습니다. 이렇게하면 파일 또는 대체 할 수있는 단위 테스트에 매우 유용 stdin로모그래퍼을 StringIO프로그램에 더 이상 변경하지 않고도. StringIO와 동일한 프로토콜을 준수하기 때문에 IO클래스가 다르더라도 둘 다 동일한 유형이며 관계가 없습니다 (둘 다 Object어느 정도 확장되는 사소한 것 제외 ).


언어가 프로그램이 클래스 유형과 해당 클래스가 구현 된 인터페이스를 동시에 선언 할 수 있도록 허용하고 구현에서 "생성자"(인터페이스에 의해 지정된 클래스의 생성자에 연결되는)를 지정할 수 있도록하는 것이 도움이 될 수 있습니다. 참조가 공개적으로 공유되는 객체 유형의 경우 선호되는 패턴은 파생 된 클래스를 만들 때만 클래스 유형을 사용하는 것입니다. 대부분의 참조는 인터페이스 유형이어야합니다. 인터페이스 생성자를 지정할 수 있으면 다음과 같은 상황에서 도움이 될 것입니다.
supercat

... 예를 들어, 코드는 특정 값 집합을 색인으로 읽을 수있는 컬렉션이 필요하지만 실제로 어떤 유형인지는 신경 쓰지 않습니다. 클래스와 인터페이스를 고유 한 유형의 유형으로 인식해야하는 확실한 이유가 있지만 현재 허용되는 언어보다 더 밀접하게 협력 할 수있는 상황이 많이 있습니다.
supercat

믹스 인, 구조체, 인터페이스 및 클래스를 공식적으로 구분하는 실험적 LISP 방언에 대한 추가 정보를 검색 할 수있는 참조 또는 키워드가 있습니까?
전화

@tel : 아니요, 죄송합니다. 아마 약 15-20 년 전이었고, 그 당시 저의 관심사는 모든 곳에있었습니다. 나는 이것을 우연히 발견했을 때 내가 무엇을 찾고 있었는지 말할 수 없었습니다.
Jörg W Mittag

Awww. 그것은이 모든 대답에서 가장 흥미로운 세부 사항이었습니다. 언어의 구현 내에서 이러한 개념의 공식적인 분리가 실제로 가능하다는 사실은 실제로 클래스 / 유형 구별을 결정하는 데 도움이되었습니다. 어쨌든 LISP를 직접 찾아 갈 것입니다. 저널 기사 / 책에서 읽었거나 대화에서 방금 들었을 때 기억하십니까?
tel

2

타입과 클래스를 구별 한 다음 서브 타이핑과 서브 클래 싱의 차이점을 살펴 보는 것이 가장 유용 할 것입니다.

이 답변의 나머지 부분에서는 논의중인 유형이 정적 유형이라고 가정합니다 (하위 유형 지정은 일반적으로 정적 컨텍스트에 있기 때문에).

나는 대부분의 언어가 적어도 부분적으로 어렴풋이 있기 때문에 유형과 클래스의 차이점을 설명하기 위해 장난감 의사 코드를 개발할 것입니다.

유형부터 시작하겠습니다. 유형은 코드에서 표현식의 레이블입니다. 이 레이블의 값과 다른 모든 레이블 값과 일치하는지 (일부 유형 시스템 고유의 일관성에 대한 여부)는 프로그램을 실행하지 않고 외부 프로그램 (유형 검사기)에 의해 결정될 수 있습니다. 이것이 바로이 라벨이 자신의 이름을 특별하고 가치있게 만드는 이유입니다.

장난감 언어로 라벨을 만들 수 있습니다.

declare type Int
declare type String

그런 다음 다양한 값을이 유형으로 레이블링 할 수 있습니다.

0 is of type Int
1 is of type Int
-1 is of type Int
...

"" is of type String
"a" is of type String
"b" is of type String
...

이러한 문장으로 타입 체커는 이제 다음과 같은 문장을 거부 할 수 있습니다.

0 is of type String

형식 시스템의 요구 사항 중 하나가 모든 식에 고유 한 형식이 있어야한다는 것입니다.

이제 얼마나 복잡한 지, 그리고 무한한 수의 표현식 유형을 할당하는 데 문제가있는 방법은 따로 두십시오. 나중에 다시 돌아올 수 있습니다.

반면에 클래스는 함께 그룹화되는 메소드 및 필드의 모음입니다 (잠재적으로는 개인 또는 공용과 같은 액세스 수정 자와 함께).

class StringClass:
  defMethod concatenate(otherString): ...
  defField size: ...

이 클래스의 인스턴스는 이러한 메소드 및 필드의 기존 정의를 작성하거나 사용할 수 있습니다.

클래스의 모든 인스턴스가 해당 유형으로 자동 레이블 지정되도록 클래스를 유형과 연관시킬 수 있습니다.

associate StringClass with String

그러나 모든 유형에 관련 클래스가 필요한 것은 아닙니다.

# Hmm... Doesn't look like there's a class for Int

또한 장난감 언어에서 모든 클래스에 유형이있는 것은 아니며, 특히 모든 표현에 유형이있는 것은 아닙니다. 일부 표현식에 유형이 있고 일부는 그렇지 않은 경우 어떤 유형 시스템 일관성 규칙이 표시 될지 상상하기가 다소 까다 롭지 만 불가능하지는 않습니다.

또한 우리의 장난감 언어에서 이러한 연관성은 고유하지 않아도됩니다. 두 클래스를 같은 유형으로 연결할 수 있습니다.

associate MyCustomStringClass with String

이제 타입 체커가 표현식의 값을 추적 할 필요는 없습니다 (대부분의 경우 그렇게하지 않거나 불가능합니다). 당신이 알고있는 레이블 만 알고 있습니다. 이전에 상기 한 바와 같이 타입 체커는 0 is of type String인위적으로 생성 된 타입 규칙으로 인해 표현식에 고유 한 유형이 있어야하고 이미 0다른 식에 레이블을 지정 했으므로 명령문을 거부 할 수있었습니다 . 의 가치에 대한 특별한 지식이 없었습니다 0.

서브 타이핑은 어떻습니까? 음의 하위 유형은 유형 검사에서 일반적인 규칙의 이름으로 다른 규칙을 완화합니다. 즉, A is subtype of B모든 유형 검사기에서 라벨을 요구하는 모든 곳 B에서 A.

예를 들어 이전에 사용했던 숫자 대신 다음과 같은 숫자를 사용할 수 있습니다.

declare type NaturalNum
declare type Int
NaturalNum is subtype of Int

0 is of type NaturalNum
1 is of type NaturalNum
-1 is of type Int
...

서브 클래 싱은 이전에 선언 된 메소드와 필드를 재사용 할 수있는 새 클래스를 선언하기위한 축약입니다.

class ExtendedStringClass is subclass of StringClass:
  # We get concatenate and size for free!
  def addQuestionMark: ...

우리의 동료 인스턴스가 없습니다 ExtendedStringClassString우리가했던 것처럼 StringClass그것은 완전히 새로운 클래스의 모든 후, 이후, 우리는 단지만큼 작성하지 않았다. 이를 통해 ExtendedStringClass타입 String체커의 관점에서 호환되지 않는 타입 을 제공 할 수 있습니다.

마찬가지로 우리는 완전히 새로운 클래스를 만들하기로 결정했습니다 수 NewClass및 수행

associate NewClass with String

이제 타입 체커의 관점에서 모든 인스턴스를 StringClass대체 할 수 있습니다 NewClass.

이론적으로 서브 타이핑과 서브 클래 싱은 완전히 다릅니다. 그러나 내가 아는 언어에는 유형과 클래스가 실제로 이런 식으로 작동합니다. 우리의 언어를 분석하고 우리의 결정에 대한 근거를 설명합시다.

첫째, 이론상 완전히 다른 클래스에 동일한 유형이 주어 지거나 클래스에 다른 클래스의 인스턴스가 아닌 값과 동일한 유형이 주어질 수 있지만 이것은 유형 검사기의 유용성을 심각하게 저해합니다. 타입 체커는 표현식 내에서 호출하는 메소드 또는 필드가 실제로 해당 값에 있는지 여부를 확인하는 기능을 효과적으로 강탈합니다. 이는 아마도 유형 검사기. 결국, 그 String레이블 아래에 실제로있는 값이 무엇인지 아는 사람 ; 예를 들어 concatenate방법 이 전혀없는 것일 수 있습니다 !

자, 모든 클래스가 자동으로 해당 클래스와 동일한 이름의 새로운 유형을 생성하고 associate해당 유형의 인스턴스를 생성하도록 규정하겠습니다 . 이를 통해 와 associate사이의 다른 이름을 제거 할 수 있습니다 .StringClassString

같은 이유로, 두 클래스의 유형 사이에 하위 유형 관계를 자동으로 설정하려고합니다. 하나는 다른 클래스의 하위 클래스입니다. 모든 서브 클래스는 부모 클래스가하는 모든 메소드와 필드를 갖도록 보장되지만 그 반대는 사실이 아닙니다. 따라서 서브 클래스가 상위 클래스의 유형이 필요할 때마다 전달할 수 있지만 서브 클래스의 유형이 필요한 경우 상위 클래스의 유형이 거부되어야합니다.

이것을 모든 사용자 정의 값이 클래스의 인스턴스 여야한다는 규정과 결합하면 is subclass of이중 의무를 제거하고를 제거 할 수 있습니다 is subtype of.

그리고 이것은 우리에게 인기있는 정적으로 유형화 된 OO 언어의 대부분이 공유하는 특성을 우리에게 알려줍니다. "원시"유형 (예를 들어 일련의 존재 int, float모든 클래스와 연관되지 않으며 사용자 정의하지되는 등). 그런 다음 동일한 이름의 유형을 자동으로 가지며 하위 유형을 지정하여 하위 클래스를 식별하는 모든 사용자 정의 클래스가 있습니다.

마지막으로 메모 할 내용은 값과 별도로 형식을 선언하는 어리 석음입니다. 대부분의 언어는 두 언어의 작성을 혼동하므로 형식 선언은 해당 형식으로 자동 레이블이 지정된 완전히 새로운 값을 생성하기위한 선언이기도합니다. 예를 들어, 클래스 선언은 일반적으로 형식과 해당 형식의 값을 인스턴스화하는 방법을 모두 만듭니다. 이것은 약간의 혼란을 없애고 생성자가있는 경우 한 번의 입력으로 유형이 많은 레이블을 무한대로 만들 수 있습니다.

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