느슨한 결합을 달성하는 데 인터페이스가 수퍼 클래스보다 더 유용한 이유는 무엇입니까?


15

( 이 질문의 목적 상, 내가 '인터페이스'라고 말할 때 나는 다른 의미로 '인터페이스'가 아닌 언어 구조를 의미합니다interface . 즉, 클래스가 외부 세계와 통신하기 위해 조작 해주세요. )

구체적인 유형 대신 추상화에 의존하는 객체를 가짐으로써 느슨한 결합을 달성 할 수 있습니다.

이 두 가지 이유에 대한 느슨한 결합을 허용 : 1 추상화가 종속 코드가 덜 파괴하는 것입니다 의미 구체적인 유형보다는 변화 가능성이 적습니다. 2 가지 콘크리트 유형은 모두 추상화에 맞기 때문에 런타임에 사용할 수 있습니다. 기존의 종속 코드를 변경할 필요없이 새로운 콘크리트 유형을 나중에 추가 할 수도 있습니다.

예를 들어, 클래스 고려 Car와 두 개의 서브 클래스를 Volvo하고 Mazda.

코드가에 의존하는 경우 런타임 중에 Cara Volvo또는 a를 사용할 수 있습니다 Mazda. 또한 나중에 종속 코드를 변경할 필요없이 추가 서브 클래스를 추가 할 수 있습니다.

또한 Car추상화 (abstract)는 Volvo또는 보다 변경 될 가능성이 적습니다 Mazda. 자동차는 일반적으로 꽤 오랫동안 동일했지만 Volvos와 Mazdas는 훨씬 더 변경 될 가능성이 있습니다. 즉, 추상화는 콘크리트 유형보다 안정적입니다.

이 모든 것들은 느슨한 결합이 무엇인지, 그리고 concretion이 아닌 추상화에 따라 어떻게 결합되는지 이해한다는 것을 보여주었습니다. (정확하지 않은 것을 쓴다면 그렇게 말하십시오).

내가 이해하지 못하는 것은 이것입니다 :

추상화는 수퍼 클래스 또는 인터페이스 일 수 있습니다.

그렇다면 왜 인터페이스가 느슨한 결합을 허용하는 능력으로 칭찬을 받는가? 수퍼 클래스를 사용하는 것과 어떻게 다른지 알 수 없습니다.

내가 볼 수있는 유일한 차이점은 다음과 같습니다. 1- 인터페이스는 단일 상속으로 제한되지 않지만 느슨한 결합이라는 주제와 관련이 없습니다. 2- 인터페이스는 구현 로직이 전혀 없으므로 더 '추상적'입니다. 그러나 여전히 그것이 왜 그렇게 큰 차이를 일으키는 지 알 수 없습니다.

간단한 수퍼 클래스는 그렇지 않지만 느슨한 결합을 허용하는 데 인터페이스가 큰 이유를 설명해주십시오.


3
"인터페이스"가있는 대부분의 언어 (예 : Java, C #)는 단일 상속 만 지원합니다. 각 클래스는 하나의 즉각적인 수퍼 클래스 만 가질 수 있으므로 하나의 객체가 여러 추상화를 지원하기에 (추상적 인) 수퍼 클래스는 너무 제한됩니다. 다중 상속으로 " 다이아몬드 문제 "를 피하는 현대적인 대안을 찾기 위해 특성 (예 : 스칼라 또는 펄의 역할 )을 확인하십시오 .
amon

@amon 그래서 느슨한 결합을 달성하려고 할 때 추상 클래스에 비해 인터페이스의 장점은 단일 상속에 의해 제한되지 않는다는 것입니까?
Aviv Cohn

아니요, 컴파일러 측면에서 비용이 많이 들기 때문에 추상 클래스를 처리 할 때 더 많은 것을해야 하지만 아마도 무시 될 수 있습니다.
pasty

2
그것은 @amon는 바른 길에처럼, 내가 발견 보이는 이 게시물 :이 있다고한다 interfaces are essential for single-inheritance languages like Java and C# because that's the only way in which you can aggregate different behaviors into a single class(인터페이스는 순수 가상 함수 단지 수업을하다 C ++와 비교 나를 리즈 어떤을).
pasty

누가 슈퍼 클래스가 나쁘다고 말했는지 알려주십시오.
Tulains Córdova

답변:


11

용어 : 언어 구조 interfaceinterface로 , 유형 또는 객체의 인터페이스를 표면으로 표현합니다 (더 나은 용어가없는 경우).

구체적인 유형 대신 추상화에 의존하는 객체를 가짐으로써 느슨한 결합을 달성 할 수 있습니다.

옳은.

이를 통해 두 가지 주요 이유로 느슨한 커플 링이 가능합니다. 1- 추상화가 콘크리트 유형보다 변경 될 가능성이 낮아서 종속 코드가 중단 될 가능성이 적습니다. 2- 다른 콘크리트 유형은 모두 추상화에 맞기 때문에 런타임에 사용할 수 있습니다. 기존의 종속 코드를 변경할 필요없이 새로운 콘크리트 유형을 나중에 추가 할 수도 있습니다.

정확하지 않습니다. 현재 언어는 일반적으로 추상화가 변경 될 것으로 예상하지는 않습니다 (그러나이를 처리 할 디자인 패턴이 있지만). 일반 사항과 세부 사항을 분리하는 것은 추상화입니다. 이것은 보통 추상 계층에 의해 수행됩니다 . 이 계층은이 추상화에 기반한 코드를 손상시키지 않고 다른 특정 사항으로 변경할 수 있습니다. 느슨한 결합이 이루어집니다. 비 OOP 예 : sort루틴이 버전 1의 Quicksort에서 버전 2의 Tim Sort로 변경 될 수 있습니다 . 따라서 정렬중인 결과에만 의존하는 코드 (즉, sort추상화에 기반한 코드 )는 실제 정렬 구현에서 분리됩니다.

위에서 표면 이라고 부르는 것은 추상화 의 일반적인 부분 입니다. OOP에서 하나의 객체가 때때로 여러 추상화를 지원해야합니다. A-확실히하지 최적의 예 : 자바의 java.util.LinkedList모두 지원 List은 "주문, 색인 모음"추상화에 대해, 그리고 지원 인터페이스 Queue(거친 용어)은 "FIFO"추상화에 관한 인터페이스를.

객체가 여러 추상화를 어떻게 지원할 수 있습니까?

C ++에는 인터페이스가 없지만 다중 상속, 가상 메소드 및 추상 클래스가 있습니다. 그런 다음 추상화는 가상 메소드를 선언하지만 정의하지 않는 추상 클래스 (즉, 즉시 인스턴스화 할 수없는 클래스)로 정의 할 수 있습니다. 그런 다음 추상화의 세부 사항을 구현하는 클래스는 해당 추상 클래스에서 상속하고 필요한 가상 메서드를 구현할 수 있습니다.

여기에서 문제는 다중 상속으로 인해 다이아몬드 문제가 발생할 수 있으며 , 클래스에서 메소드 구현을 검색하는 순서 (MRO : 메소드 해결 순서)가 "모순"을 초래할 수 있습니다. 이에 대한 두 가지 응답이 있습니다.

  1. 정상적인 순서를 정의하고 현명하게 선형화 할 수없는 순서를 거부하십시오. C3 MRO는 매우 합리적이고 잘 작동합니다. 1996 년에 출판되었다.

  2. 쉬운 경로를 통해 여러 상속을 거부하십시오.

Java는 후자의 옵션을 취하여 단일 행동 상속을 선택했습니다. 그러나 여전히 다중 추상화를 지원하는 객체의 기능이 필요합니다. 따라서 메소드 정의를 지원하지 않고 선언 만 지원하는 인터페이스를 사용해야합니다.

결과적으로 MRO는 명확하고 (각 수퍼 클래스를 순서대로 살펴보십시오), 객체는 여러 추상화에 대해 여러 표면을 가질 수 있습니다.

이것은 종종 약간의 행동이 표면의 일부이기 때문에 다소 불만족스러운 것으로 판명되었습니다. Comparable인터페이스를 고려하십시오 .

interface Comparable<T> {
    public int cmp(T that);
    public boolean lt(T that);  // less than
    public boolean le(T that);  // less than or equal
    public boolean eq(T that);  // equal
    public boolean ne(T that);  // not equal
    public boolean ge(T that);  // greater than or equal
    public boolean gt(T that);  // greater than
}

이것은 매우 사용자 친화적이지만 (편리한 방법이 많은 멋진 API) 구현하기가 지루합니다. 우리는 인터페이스가 오직 cmp하나의 필수 메소드 측면에서 자동으로 다른 메소드를 포함 하고 구현 하기를 원합니다 . 믹스 인 , 그러나 더 중요한 것은 특성 [ 1 ], [ 2 ]가 다중 상속의 함정에 빠지지 않고이 문제를 해결합니다.

이는 특성이 MRO에 실제로 참여하지 않도록 특성 구성을 정의하여 수행됩니다. 대신 정의 된 메소드가 구현 클래스로 구성됩니다.

Comparable인터페이스는 스칼라로서 표현 될 수있다

trait Comparable[T] {
    def cmp(that: T): Int
    def lt(that: T): Boolean = this.cmp(that) <  0
    def le(that: T): Boolean = this.cmp(that) <= 0
    ...
}

클래스가 해당 특성을 사용하면 다른 메소드가 클래스 정의에 추가됩니다.

// "extends" isn't different from Java's "implements" in this case
case class Inty(val x: Int) extends Comparable[Inty] {
    override def cmp(that: Inty) = this.x - that.x
    // lt etc. get added automatically
}

그래서 Inty(4) cmp Inty(6)-2Inty(4) lt Inty(6)true.

많은 언어가 특성을 일부 지원하며 "MOP (Metaobject Protocol)"가있는 언어는 특성을 추가 할 수 있습니다. 최근 Java 8 업데이트에는 특성과 유사한 기본 메소드가 추가되었습니다 (인터페이스의 메소드는 대체 구현을 가질 수 있으므로 클래스를 구현하여 이러한 메소드를 구현하는 것이 선택 사항 임).

불행하게도, 특성은 상당히 최근의 발명 (2002)이므로 더 큰 주류 언어에서는 드물다.


좋은 대답이지만 단일 상속 언어 가 컴포지션이있는 인터페이스를 사용하여 여러 상속을 퍼지 할 있다고 덧붙입니다.

4

내가 이해하지 못하는 것은 이것입니다 :

추상화는 수퍼 클래스 또는 인터페이스 일 수 있습니다.

그렇다면 왜 인터페이스가 느슨한 결합을 허용하는 능력으로 칭찬을 받는가? 수퍼 클래스를 사용하는 것과 어떻게 다른지 알 수 없습니다.

첫째, 서브 타이핑과 추상화는 서로 다른 두 가지입니다. 하위 유형 지정은 단순히 한 유형의 값을 다른 유형의 값으로 대체 할 수 있음을 의미합니다. 유형이 추상 일 필요는 없습니다.

더 중요한 것은 서브 클래스가 수퍼 클래스의 구현 세부 사항에 직접적으로 의존한다는 것입니다. 이것이 가장 강력한 결합입니다. 실제로, 기본 클래스가 상속을 염두에두고 설계되지 않은 경우, 기본 클래스를 변경하지 않으면 해당 동작을 변경하지 않아도 하위 클래스가 중단 될 수 있으며, 중단 이 발생할 경우 우선 순위 를 알 수 있는 방법이 없습니다 . 이것을 취약한 기본 클래스 문제라고 합니다.

인터페이스를 구현한다고해서 인터페이스 자체를 제외하고는 아무런 동작도하지 않습니다.


대답 해줘서 고마워. 내가 이해하는지 확인하려면 : A라는 객체가 C라는 추상화의 구체적인 구현 대신 B라는 추상화에 의존하기를 원할 때 B가 확장 된 수퍼 클래스 대신 C로 구현 된 인터페이스 인 것이 더 낫습니다. C. 그 이유는 다음과 같습니다. C 서브 클래 싱 B는 C를 B에 밀접하게 연결합니다. B가 변경되면-C가 변경됩니다. 그러나 B를 구현하는 C (인터페이스 인 B)는 B를 C에 연결하지 않습니다. B는 C가 구현해야하는 방법의 목록 일 뿐이므로 긴밀한 연결은 없습니다. 그러나 객체 A (종속)에 대해서는 B가 클래스인지 인터페이스인지는 중요하지 않습니다.
Aviv Cohn

옳은? ..... .....
Aviv Cohn

왜 인터페이스가 어떤 것에 연결되어 있다고 생각하십니까?
Michael Shaw

나는이 대답이 머리에 못을 박았다고 생각합니다. 나는 C ++을 꽤 많이 사용하고, 다른 답변 중 하나에서 언급했듯이 C ++에는 인터페이스가 없지만 "순수 가상"(즉, 어린이가 구현)으로 남겨둔 모든 메소드와 함께 수퍼 클래스를 사용하여 가짜로 만듭니다. 요점은 위임 된 기능과 함께 무언가를하는 기본 클래스를 쉽게 만들 수 있다는 것입니다. 많은 경우에 많은 저와 제 동료들은 새로운 유스 케이스가 생겨나 고 그 공유 기능을 무효화한다는 것을 알게됩니다. 공유 기능이 필요한 경우 도우미 클래스를 만들기가 쉽습니다.
J Trana

@Prog 당신의 생각은 대부분 정확하지만 추상화와 서브 타이핑은 별개의 것입니다. 당신이 말할 때 당신은 you want an object named A to depend on an abstraction named B instead of a concrete implementation of that abstraction named C클래스가 어떻게 든 추상적이지 않다고 가정합니다. 추상화는 구현 세부 사항을 숨기는 것이므로 개인 필드가있는 클래스는 동일한 공용 메소드가있는 인터페이스와 마찬가지로 추상적입니다.
Doval

1

자식은 부모에 의존하기 때문에 부모와 자식 클래스 사이에는 커플 링이 있습니다.

클래스 A가 있고 클래스 B가 클래스 A를 상속한다고 가정 해보십시오. 우리가 클래스 A에 들어가서 상황을 바꾸면 클래스 B도 바뀌게됩니다.

인터페이스 I이 있고 클래스 B가 구현한다고 가정 해보십시오. 인터페이스 I을 변경하면 클래스 B가 더 이상 구현하지 않을 수 있지만 클래스 B는 변경되지 않습니다.


다운 보터가 이유가 있었는지, 아니면 단지 나쁜 하루를 보내고 있었는지 궁금합니다.
Michael Shaw

1
공감하지는 않았지만 첫 번째 문장과 관련이 있다고 생각합니다. 자식 클래스는 다른 방법이 아니라 부모 클래스와 연결됩니다. 부모는 자녀에 대해 아무것도 알 필요가 없지만 자녀는 부모에 대한 친밀한 지식이 필요합니다.

@ JohnGaughan : 의견 감사합니다. 명확성을 위해 편집되었습니다.
Michael Shaw
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.