your programing

인터페이스 대 기본 클래스

lovepro 2020. 9. 29. 08:17
반응형

인터페이스 대 기본 클래스


언제 인터페이스를 사용해야하며 언제 기본 클래스를 사용해야합니까?

메서드의 기본 구현을 실제로 정의하지 않으려면 항상 인터페이스 여야합니까?

내가 개와 고양이 수업이 있다면. PetBase 대신 IPet을 구현해야하는 이유는 무엇입니까? ISheds 또는 IBarks (IMakesNoise?)를위한 인터페이스가 있다는 것을 이해할 수 있습니다. 왜냐하면 그것들은 애완 동물 단위로 애완 동물에 배치 할 수 있기 때문입니다. 그러나 일반적인 애완 동물에 어떤 것을 사용해야할지 모르겠습니다.


Dog 및 Cat 클래스의 예를 살펴보고 C #을 사용하여 설명해 보겠습니다.

개와 고양이는 모두 동물, 특히 네 발 달린 포유류입니다 (동물은 너무 일반적입니다). 둘 다에 대해 Mammal 추상 클래스가 있다고 가정 해 보겠습니다.

public abstract class Mammal

이 기본 클래스에는 다음과 같은 기본 메서드가있을 것입니다.

  • 먹이다
  • 항해사

이들 모두는 두 종간에 어느 정도 동일한 구현을 갖는 행동입니다. 이를 정의하려면 다음이 필요합니다.

public class Dog : Mammal
public class Cat : Mammal

이제 우리가 보통 동물원에서 보게 될 다른 포유류가 있다고 가정 해 봅시다.

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

이 때문에 여전히 기능의 핵심에 유효하다 Feed()Mate()여전히 동일합니다.

그러나 기린, 코뿔소, 하마는 애완 동물을 만들 수있는 동물이 아닙니다. 인터페이스가 유용 할 것입니다.

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

위 계약의 구현은 고양이와 개간에 동일하지 않습니다. 상속 할 추상 클래스에 구현을 넣는 것은 나쁜 생각입니다.

이제 Dog 및 Cat 정의가 다음과 같이 표시됩니다.

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

이론적으로는 상위 기본 클래스에서 재정의 할 수 있지만 기본적으로 인터페이스를 사용하면 상속 할 필요없이 클래스에 필요한 것만 추가 할 수 있습니다.

따라서 일반적으로 하나의 추상 클래스에서만 상속 할 수 있지만 (대부분의 정적으로 형식화 된 OO 언어, 즉 예외는 C ++ 포함) 여러 인터페이스를 구현할 수 있기 때문에 필요한 기준 에 따라 엄격하게 객체를 구성 할 수 있습니다 .


글쎄, Josh Bloch는 Effective Java 2d 에서 자신을 말했습니다 .

추상 클래스보다 인터페이스 선호

몇 가지 주요 사항 :

  • 기존 클래스를 쉽게 개조하여 새 인터페이스를 구현할 수 있습니다 . 필요한 메서드가 아직없는 경우 추가하고 클래스 선언에 구현 절을 추가하기 만하면됩니다.

  • 인터페이스는 믹스 인을 정의하는 데 이상적입니다 . 간단히 말해서 mixin은 클래스가 "기본 유형"에 추가하여 일부 선택적 동작을 제공함을 선언하기 위해 구현할 수있는 유형입니다. 예를 들어, Comparable은 클래스가 인스턴스가 상호 비교 가능한 다른 객체와 관련하여 정렬되어 있음을 선언 할 수 있도록하는 믹스 인 인터페이스입니다.

  • 인터페이스는 비 계층 적 유형 프레임 워크의 구성을 허용합니다 . 유형 계층 구조는 일부 항목을 구성하는 데 적합하지만 다른 항목은 견고한 계층 구조로 깔끔하게 떨어지지 않습니다.

  • 인터페이스 는 래퍼 클래스 관용구를 통해 안전하고 강력한 기능 향상가능하게합니다 . 추상 클래스를 사용하여 유형을 정의하는 경우 기능을 추가하려는 프로그래머는 상속을 사용하는 것 외에는 대안이 없습니다.

더욱이, 당신은 당신이 내보내는 각각의 중요하지 않은 인터페이스와 함께 갈 추상 골격 구현 클래스를 제공함으로써 인터페이스와 추상 클래스의 장점을 결합 할 수 있습니다.

반면에 인터페이스는 진화하기가 매우 어렵습니다. 인터페이스에 메소드를 추가하면 모든 구현이 중단됩니다.

추신 : 책을 사십시오. 훨씬 더 자세합니다.


인터페이스와 기본 클래스는 두 가지 다른 형태의 관계를 나타냅니다.

상속 (기본 클래스)은 "is-a"관계를 나타냅니다. 예를 들어 개나 고양이는 애완 동물입니다. 이 관계는 항상 클래스 의 (단일) 목적나타냅니다 ( "단일 책임 원칙" 과 함께 ).

반면에 인터페이스 는 클래스의 추가 기능나타냅니다 . "is"와 같이 "is"관계라고하겠습니다. Foo따라서 IDisposableC # 인터페이스입니다.


현대적인 스타일은 IPet PetBase 를 정의하는 것입니다.

인터페이스의 장점은 다른 코드가 다른 실행 코드와 연결되지 않고 사용할 수 있다는 것입니다. 완전히 "깨끗합니다." 또한 인터페이스를 혼합 할 수 있습니다.

그러나 기본 클래스는 간단한 구현과 공통 유틸리티에 유용합니다. 따라서 시간과 코드를 절약하기 위해 추상 기본 클래스도 제공하십시오.


인터페이스

  • 두 모듈 간의 계약을 정의합니다. 구현할 수 없습니다.
  • 대부분의 언어를 사용하면 여러 인터페이스를 구현할 수 있습니다.
  • 인터페이스 수정은 주요 변경 사항입니다. 모든 구현은 다시 컴파일 / 수정해야합니다.
  • 모든 회원은 공개됩니다. 구현은 모든 구성원을 구현해야합니다.
  • 인터페이스는 디커플링에 도움이됩니다. 모의 프레임 워크를 사용하여 인터페이스 뒤에있는 모든 것을 모의 할 수 있습니다.
  • 인터페이스는 일반적으로 일종의 동작을 나타냅니다.
  • 인터페이스 구현은 서로 분리 / 분리됩니다.

기본 클래스

  • 파생을 통해 무료로 얻을 수 있는 기본 구현 을 추가 할 수 있습니다.
  • C ++를 제외하고 하나의 클래스에서만 파생 될 수 있습니다. 여러 클래스에서 가능하더라도 일반적으로 나쁜 생각입니다.
  • 기본 클래스를 변경하는 것은 비교적 쉽습니다. 파생은 특별한 작업을 수행 할 필요가 없습니다.
  • 기본 클래스는 파생으로 액세스 할 수있는 보호 및 공용 함수를 선언 할 수 있습니다.
  • Abstract Base 클래스는 인터페이스처럼 쉽게 조롱 할 수 없습니다.
  • 기본 클래스는 일반적으로 유형 계층 구조 (IS A)를 나타냅니다.
  • 클래스 파생은 일부 기본 동작에 따라 달라질 수 있습니다 (부모 구현에 대한 복잡한 지식이 있음). 한 사람의 기본 구현을 변경하고 다른 사람은 깨 뜨리면 상황이 지저분해질 수 있습니다.

일반적으로 추상 클래스보다 인터페이스를 선호해야합니다. 추상 클래스를 사용하는 한 가지 이유는 구체적인 클래스간에 공통 구현이있는 경우입니다. 물론 인터페이스 (IPet)를 선언하고 추상 클래스 (PetBase)가 해당 인터페이스를 구현하도록해야합니다. 작고 별개의 인터페이스를 사용하면 다중을 사용하여 유연성을 더욱 향상시킬 수 있습니다. 인터페이스는 경계를 넘어선 유형의 최대 유연성과 이식성을 허용합니다. 경계를 넘어 참조를 전달할 때는 항상 구체적인 유형이 아닌 인터페이스를 전달하십시오. 이를 통해 수신 측은 구체적인 구현을 결정할 수 있으며 최대 유연성을 제공합니다. 이것은 TDD / BDD 방식으로 프로그래밍 할 때 절대적으로 사실입니다.

The Gang of Four는 자신의 책에서 "상속은 부모의 구현에 대한 세부 사항에 하위 클래스를 노출하기 때문에 종종 '상속이 캡슐화를 깨뜨린 다"고 말합니다. 나는 이것이 사실이라고 믿습니다.


이것은 .NET에 특화되어 있지만, Framework Design Guidelines 책에서는 일반적으로 클래스가 진화하는 프레임 워크에서 더 많은 유연성을 제공한다고 주장합니다. 인터페이스가 출시되면 해당 인터페이스를 사용하는 코드를 깨지 않고는 변경할 수 없습니다. 그러나 클래스를 사용하면 클래스를 수정할 수 있으며 링크 된 코드를 중단 할 수 없습니다. 새로운 기능 추가를 포함하여 올바른 수정을하는 한 코드를 확장하고 발전시킬 수 있습니다.

Krzysztof Cwalina는 81 페이지에 다음과 같이 말합니다.

.NET Framework의 세 가지 버전을 진행하는 동안 우리 팀의 상당수의 개발자와이 지침에 대해 이야기했습니다. 처음에 지침에 동의하지 않는 사람들을 포함하여 많은 사람들은 일부 API를 인터페이스로 제공 한 것을 후회한다고 말했습니다. 수업이 나간 것을 후회 한 사례는 한 번도 들어 본 적이 없습니다.

그것은 확실히 인터페이스를위한 장소가 있다고 말하고 있습니다. 일반적인 지침으로 인터페이스를 구현하는 방법의 예로서 항상 인터페이스의 추상 기본 클래스 구현을 제공합니다. 최상의 경우 기본 클래스는 많은 작업을 절약합니다.


후안,

저는 인터페이스를 클래스를 특성화하는 방법으로 생각하고 싶습니다. YorkshireTerrier와 같은 특정 개 품종 클래스는 부모 개 클래스의 후손 일 수 있지만 IFurry, IStubby 및 IYippieDog도 구현합니다. 그래서 클래스는 클래스가 무엇인지 정의하지만 인터페이스는 그것에 대해 알려줍니다.

이것의 장점은 예를 들어 모든 IYippieDog를 모아서 내 Ocean 컬렉션에 넣을 수 있다는 것입니다. 따라서 이제 특정 개체 집합에 도달하여 클래스를 너무 면밀히 검사하지 않고도 내가보고있는 기준에 맞는 개체를 찾을 수 있습니다.

인터페이스는 실제로 클래스의 공개 동작의 하위 집합을 정의해야합니다. 구현하는 모든 클래스에 대한 모든 공용 동작을 정의하면 일반적으로 존재할 필요가 없습니다. 그들은 나에게 유용한 것을 말하지 않습니다.

이 생각은 모든 클래스에 인터페이스가 있어야하고 인터페이스에 코드를 작성해야한다는 생각과는 반대입니다. 괜찮지 만 클래스에 대한 일대일 인터페이스가 많아서 혼란스러워집니다. 나는 그 아이디어가 실제로 할 일이 전혀 들지 않으며 이제 쉽게 물건을 안팎으로 바꿀 수 있다는 것을 이해합니다. 그러나 나는 그것을 거의하지 않는다는 것을 알았다. 대부분의 경우 기존 클래스를 제자리에서 수정하고 해당 클래스의 공용 인터페이스를 변경해야 할 경우 항상했던 것과 똑같은 문제가 있습니다. 단, 이제 두 곳에서 변경해야합니다.

그래서 나처럼 생각한다면 고양이와 개는 IPettable이라고 분명히 말할 것입니다. 둘 다 일치하는 특성입니다.

이것의 다른 부분은 동일한 기본 클래스를 가져야한다는 것입니다. 문제는 그들이 동일한 것으로 광범위하게 취급되어야 하는가하는 것입니다. 확실히 그들은 둘 다 동물이지만 우리가 그들을 함께 사용하는 방법에 적합합니다.

모든 Animal 클래스를 모아서 Ark 컨테이너에 넣고 싶다고 가정 해 보겠습니다.

아니면 포유류 여야합니까? 아마도 우리는 일종의 교차 동물 착유 공장이 필요합니까?

서로 연결될 필요가 있습니까? 둘 다 IPettable이라는 것을 아는 것으로 충분합니까?

저는 종종 하나의 클래스 만 필요할 때 전체 클래스 계층 구조를 도출하려는 욕구를 종종 느낍니다. 언젠가는 필요할지도 모르지만 보통은 절대하지 않습니다. 그래도 문제를 해결하려면 많은 일을해야합니다. 그것은 내가 만드는 첫 번째 클래스가 개가 아니기 때문입니다. 나는 그렇게 운이 좋지 않고 대신 오리너구리입니다. 이제 전체 클래스 계층 구조가 기괴한 경우를 기반으로하고 낭비되는 코드가 많이 있습니다.

어떤 시점에서 모든 고양이가 IPettable이 아니라는 것을 발견 할 수도 있습니다 (털이없는 고양이처럼). 이제 해당 인터페이스를 적합한 모든 파생 클래스로 이동할 수 있습니다. 갑자기 Cats가 더 이상 PettableBase에서 파생되지 않는 변경 사항이 훨씬 적다는 것을 알 수 있습니다.


다음은 인터페이스 및 기본 클래스의 기본적이고 간단한 정의입니다.

  • 기본 클래스 = 개체 상속.
  • 인터페이스 = 기능적 상속.

건배


가능한 한 상속 대신 구성을 사용하는 것이 좋습니다. 인터페이스를 사용하지만 기본 구현에는 멤버 개체를 사용합니다. 이렇게하면 특정 방식으로 작동하도록 개체를 구성하는 팩토리를 정의 할 수 있습니다. 동작을 변경하려면 다른 유형의 하위 객체를 만드는 새로운 팩토리 메서드 (또는 추상 팩토리)를 만듭니다.

경우에 따라 모든 변경 가능한 동작이 도우미 개체에 정의되어있는 경우 기본 개체에 인터페이스가 전혀 필요하지 않음을 알 수 있습니다.

따라서 IPet 또는 PetBase 대신 IFurBehavior 매개 변수가있는 Pet로 끝날 수 있습니다. IFurBehavior 매개 변수는 PetFactory의 CreateDog () 메소드에 의해 설정됩니다. shed () 메소드에 대해 호출되는 것은이 매개 변수입니다.

이렇게하면 코드가 훨씬 더 유연하고 대부분의 간단한 객체가 매우 기본적인 시스템 전체 동작을 처리한다는 것을 알게 될 것입니다.

다중 상속 언어에서도이 패턴을 권장합니다.


Java World 기사 에서 잘 설명했습니다.

개인적으로 나는 인터페이스를 정의하기 위해 인터페이스를 사용하는 경향이 있습니다. 즉, 시스템 설계의 일부로 무언가에 액세스해야하는 방식을 지정합니다.

하나 이상의 인터페이스를 구현하는 클래스가있는 것은 드문 일이 아닙니다.

내가 다른 것에 대한 기초로 사용하는 추상 클래스.

다음은 위에서 언급 한 JavaWorld.com 기사 에서 발췌 한 것입니다 . 작성자 Tony Sintes, 01/04/20/01


인터페이스 대 추상 클래스

인터페이스와 추상 클래스를 선택하는 것은 둘 중 하나 또는 명제가 아닙니다. 디자인을 변경해야하는 경우 인터페이스로 만드십시오. 그러나 일부 기본 동작을 제공하는 추상 클래스가있을 수 있습니다. 추상 클래스는 애플리케이션 프레임 워크 내에서 훌륭한 후보입니다.

추상 클래스를 사용하면 몇 가지 동작을 정의 할 수 있습니다. 그들은 당신의 하위 클래스가 다른 사람들을 제공하도록 강요합니다. 예를 들어 애플리케이션 프레임 워크가있는 경우 추상 클래스는 이벤트 및 메시지 처리와 같은 기본 서비스를 제공 할 수 있습니다. 이러한 서비스를 통해 애플리케이션이 애플리케이션 프레임 워크에 플러그인 할 수 있습니다. 그러나 응용 프로그램 만 수행 할 수있는 응용 프로그램 별 기능이 있습니다. 이러한 기능에는 종종 응용 프로그램에 따라 달라지는 시작 및 종료 작업이 포함될 수 있습니다. 따라서 해당 동작 자체를 정의하는 대신 추상 기본 클래스는 추상 종료 및 시작 메서드를 선언 할 수 있습니다. 기본 클래스는 이러한 메서드가 필요하다는 것을 알고 있지만 추상 클래스는 클래스가 이러한 작업을 수행하는 방법을 모른다는 것을 인정하도록합니다. 작업을 시작해야한다는 것만 알고 있습니다. 시작할 때가되면 추상 클래스는 시작 메서드를 호출 할 수 있습니다. 기본 클래스가이 메서드를 호출하면 Java는 자식 클래스에서 정의한 메서드를 호출합니다.

많은 개발자는 추상 메서드를 정의하는 클래스도 해당 메서드를 호출 할 수 있다는 사실을 잊어 버립니다. 추상 클래스는 계획된 상속 계층 구조를 만드는 훌륭한 방법입니다. 또한 클래스 계층 구조의 리프가 아닌 클래스에도 적합합니다.

클래스 대 인터페이스

어떤 사람들은 인터페이스 측면에서 모든 클래스를 정의해야한다고 말하지만 권장 사항은 약간 극단적 인 것 같습니다. 디자인의 무언가가 자주 변경 될 때 인터페이스를 사용합니다.

예를 들어, 전략 패턴을 사용하면 새로운 알고리즘과 프로세스를 사용하는 개체를 변경하지 않고도 프로그램으로 교체 할 수 있습니다. 미디어 플레이어는 CD, MP3 및 wav 파일을 재생하는 방법을 알고있을 수 있습니다. 물론 이러한 재생 알고리즘을 플레이어에 하드 코딩하고 싶지는 않습니다. AVI와 같은 새로운 형식을 추가하기가 어렵습니다. 또한 코드는 쓸모없는 case 문으로 가득 차 있습니다. 그리고 상해에 대한 모욕을 더하려면 새 알고리즘을 추가 할 때마다 해당 사례 설명을 업데이트해야합니다. 대체로 이것은 프로그래밍에 대한 객체 지향적 인 방법이 아닙니다.

With the Strategy pattern, you can simply encapsulate the algorithm behind an object. If you do that, you can provide new media plug-ins at any time. Let's call the plug-in class MediaStrategy. That object would have one method: playStream(Stream s). So to add a new algorithm, we simply extend our algorithm class. Now, when the program encounters the new media type, it simply delegates the playing of the stream to our media strategy. Of course, you'll need some plumbing to properly instantiate the algorithm strategies you will need.

인터페이스를 사용하기 좋은 곳입니다. 우리는 디자인에서 변경 될 위치를 명확하게 나타내는 전략 패턴을 사용했습니다. 따라서 전략을 인터페이스로 정의해야합니다. 객체가 특정 유형을 갖기를 원할 때 일반적으로 상속보다 인터페이스를 선호해야합니다. 이 경우 MediaStrategy입니다. 유형 ID에 대한 상속에 의존하는 것은 위험합니다. 특정 상속 계층에 고정됩니다. Java는 다중 상속을 허용하지 않으므로 유용한 구현이나 더 많은 유형 ID를 제공하는 것을 확장 할 수 없습니다.


또한 OO에서 휩쓸 리지 않도록하고 ( 블로그 참조 ) 항상 필요한 동작에 따라 객체를 모델링합니다. 필요한 동작이 동물의 일반 이름과 종 뿐인 앱을 디자인하는 경우에만 필요합니다. 세계의 모든 가능한 동물에 대한 수백만 개의 클래스 대신 이름에 대한 속성을 가진 하나의 클래스 Animal.


대략적인 어림짐작이 있습니다

기능 : 모든 부분에서 다를 수 있음 : 인터페이스.

데이터 및 기능, 부분은 거의 동일하고 부분은 다릅니다 : 추상 클래스.

데이터 및 기능, 실제로 작동하는 경우 약간만 변경하면 확장 : 일반 (콘크리트) 클래스

데이터 및 기능, 계획된 변경 없음 : 최종 수정자가있는 일반 (콘크리트) 클래스.

데이터 및 기능 : 읽기 전용 : 열거 형 멤버.

이것은 매우 거칠고 준비되어 있으며 엄격하게 정의되지는 않았지만 모든 것이 읽기 전용 파일처럼 약간 고정 된 열거 형으로 모든 것이 변경되도록 의도 된 인터페이스의 스펙트럼이 있습니다.


인터페이스는 작아야합니다. 정말 작습니다. 실제로 개체를 분해하는 경우 인터페이스에는 매우 구체적인 메서드와 속성이 몇 개만 포함될 것입니다.

추상 클래스는 바로 가기입니다. PetBase의 모든 파생물이 한 번 코딩하고 수행 할 수있는 공유가 있습니까? 그렇다면 추상 수업 시간입니다.

추상 클래스도 제한적입니다. 그들은 당신에게 자식 객체를 생성하는 훌륭한 지름길을 제공하지만 주어진 객체는 하나의 추상 클래스 만 구현할 수 있습니다. 여러 번, 이것이 Abstract 클래스의 한계라는 것을 알게되었고, 이것이 제가 많은 인터페이스를 사용하는 이유입니다.

추상 클래스는 여러 인터페이스를 포함 할 수 있습니다. PetBase 추상 클래스는 IPet (애완 동물에게 소유자가 있음) 및 IDigestion (애완 동물이 먹거나 적어도 그래야 함)을 구현할 수 있습니다. 그러나 모든 애완 동물이 포유류가 아니고 모든 포유류가 애완 동물이 아니기 때문에 PetBase는 아마도 IMammal을 구현하지 않을 것입니다. PetBase를 확장하고 IMammal을 추가하는 MammalPetBase를 추가 할 수 있습니다. FishBase는 PetBase를 가질 수 있고 IFish를 추가 할 수 있습니다. IFish는 ISwim 및 IUnderwaterBreather를 인터페이스로 사용합니다.

예, 제 예제는 간단한 예제에 대해 지나치게 복잡하지만 인터페이스와 추상 클래스가 함께 작동하는 방식에 대한 훌륭한 점의 일부입니다.


인터페이스를 통한 기본 클래스의 경우는 Submain .NET 코딩 지침에 잘 설명되어 있습니다.

기본 클래스 대 인터페이스 인터페이스 유형은 값에 대한 부분적인 설명이며 잠재적으로 많은 개체 유형에서 지원됩니다. 가능하면 인터페이스 대신 기본 클래스를 사용하십시오. 버전 관리 관점에서 클래스는 인터페이스보다 더 유연합니다. 클래스를 사용하면 버전 1.0을 제공 한 다음 버전 2.0에서 클래스에 새 메서드를 추가 할 수 있습니다. 메서드가 추상이 아닌 한 기존 파생 클래스는 변경되지 않고 계속 작동합니다.

인터페이스는 구현 상속을 지원하지 않기 때문에 클래스에 적용되는 패턴이 인터페이스에 적용되지 않습니다. 인터페이스에 메서드를 추가하는 것은 기본 클래스에 추상 메서드를 추가하는 것과 같습니다. 인터페이스를 구현하는 모든 클래스는 새로운 메서드를 구현하지 않기 때문에 중단됩니다. 인터페이스는 다음 상황에 적합합니다.

  1. 관련되지 않은 여러 클래스가 프로토콜을 지원하려고합니다.
  2. 이러한 클래스에는 이미 설정된 기본 클래스가 있습니다 (예 : 일부는 UI (사용자 인터페이스) 컨트롤이고 일부는 XML 웹 서비스 임).
  3. 집계가 적절하지 않거나 실행 가능하지 않습니다. 다른 모든 상황에서는 클래스 상속이 더 나은 모델입니다.

출처 : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C #은 지난 14 년 동안 성숙하고 진화 한 멋진 언어입니다. 성숙한 언어는 우리가 처리 할 수있는 많은 언어 기능을 제공하기 때문에 개발자에게 매우 좋습니다.

그러나 많은 힘으로 많은 책임이 있습니다. 이러한 기능 중 일부는 오용 될 수 있으며 때로는 한 기능을 다른 기능보다 사용하도록 선택하는 이유를 이해하기 어려울 수 있습니다. 수년 동안 많은 개발자들이 어려움을 겪는 것을 보아온 기능은 언제 인터페이스를 사용하거나 추상 클래스를 사용하도록 선택할 것인지입니다. 둘 다 장점과 단점이 있으며 각각을 사용할 정확한 시간과 장소가 있습니다. 그러나 우리는 어떻게 결정합니까 ???

둘 다 유형 간의 공통 기능 재사용을 제공합니다. 가장 명백한 차이점은 인터페이스가 기능에 대한 구현을 제공하지 않는 반면 추상 클래스를 사용하면 일부 "기본"또는 "기본"동작을 구현 한 다음 필요한 경우 클래스 파생 유형으로이 기본 동작을 "재정의"할 수 있다는 것입니다. .

이것은 모두 훌륭하고 코드의 재사용을 제공하며 소프트웨어 개발의 DRY (Do n't Repeat Yourself) 원칙을 준수합니다. 추상 클래스는 "is a"관계가있을 때 사용하기에 좋습니다.

예 : 골든 리트리버는 "개"유형입니다. 푸들도 마찬가지입니다. 모든 개가 할 수 있듯이 둘 다 짖을 수 있습니다. 그러나 푸들 공원이 "기본"개 껍질과는 상당히 다르다고 말할 수 있습니다. 따라서 다음과 같이 구현하는 것이 합리적 일 수 있습니다.

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

보시다시피 이것은 코드를 DRY로 유지하고 유형이 특수한 경우 구현 대신 기본 Bark에 의존 할 수있을 때 기본 클래스 구현을 호출 할 수있는 좋은 방법입니다. GoldenRetriever, Boxer, Lab과 같은 클래스는 모두 Dog 추상 클래스를 구현하기 때문에 "기본"(베이스 클래스) Bark를 무료로 상속 할 수 있습니다.

그러나 나는 당신이 이미 그것을 알고 있다고 확신합니다.

You are here because you want to understand why you might want to choose an interface over an abstract class or vice versa. Well one reason you may want to choose an interface over an abstract class is when you don’t have or want to prevent a default implementation. This is usually because the types that are implementing the interface not related in an “is a” relationship. Actually, they don’t have to be related at all except for the fact that each type “is able” or has “the ablity” to do something or have something.

이제 도대체 무슨 뜻입니까? 예를 들어, 인간은 오리가 아닙니다. 오리는 인간이 아닙니다. 꽤 분명합니다. 그러나 오리와 인간은 모두 수영을 할 수있는“능력”을 가지고 있습니다 (인간이 1 학년 때 수영 수업을 통과했다면 :). 또한 오리는 인간이 아니거나 그 반대이기 때문에 이것은 "is a"현실이 아니라 "is a"관계이며 인터페이스를 사용하여 다음을 설명 할 수 있습니다.

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

위의 코드와 같은 인터페이스를 사용하면 무언가를 "할 수있는"메소드에 객체를 전달할 수 있습니다. 코드는 그것이 어떻게되는지 신경 쓰지 않습니다. 아는 것은 해당 개체에 대해 Swim 메서드를 호출 할 수 있고 해당 개체는 유형에 따라 런타임에 어떤 동작이 수행되는지 알 수 있다는 것입니다.

다시 한 번, 이렇게하면 동일한 핵심 함수 (ShowHowHumanSwims (human), ShowHowDuckSwims (duck) 등)를 수행하기 위해 개체를 호출하는 여러 메서드를 작성할 필요가 없도록 코드가 건조 상태를 유지하는 데 도움이됩니다.

여기에서 인터페이스를 사용하면 호출 메서드가 어떤 유형인지 또는 동작이 구현되는 방식에 대해 걱정할 필요가 없습니다. 인터페이스가 주어지면 각 개체가 Swim 메서드를 구현해야하므로 자체 코드에서 호출하고 Swim 메서드의 동작이 자체 클래스 내에서 처리되도록하는 것이 안전하다는 것을 알고 있습니다.

요약:

그래서 저의 기본 규칙은 클래스 계층 구조에 대한 "기본"기능을 구현하거나 작업중인 클래스 또는 유형이 "is a"관계를 공유 할 때 추상 클래스를 사용하는 것입니다 (예 : poodle "is a ”개 유형).

반면에 "is a"관계는 없지만 무언가를하거나 무언가를 가질 수있는 "능력"을 공유하는 유형이있는 경우 인터페이스를 사용합니다 (예 : Duck은 인간이 아닙니다. 그러나 duck과 인간은 공유합니다. "수영 능력").

추상 클래스와 인터페이스 간의 또 다른 차이점은 클래스가 일대 다 인터페이스를 구현할 수 있지만 클래스는 하나의 추상 클래스 (또는 해당 문제에 대한 모든 클래스)에서만 상속 할 수 있다는 것입니다. 예, 클래스를 중첩하고 상속 계층 구조를 가질 수 있지만 (많은 프로그램에서 수행해야하는) 하나의 파생 클래스 정의에서 두 클래스를 상속 할 수 없습니다 (이 규칙은 C #에 적용됩니다. 다른 언어에서는 일반적으로이를 수행 할 수 있습니다. 이러한 언어의 인터페이스가 부족하기 때문입니다.)

또한 인터페이스를 사용하여 ISP (Interface Segregation Principle)를 준수 할 때도 기억하십시오. ISP는 어떤 클라이언트도 사용하지 않는 방법에 의존하도록 강요해서는 안된다고 말합니다. 이러한 이유로 인터페이스는 특정 작업에 중점을 두어야하며 일반적으로 매우 작습니다 (예 : IDisposable, IComparable).

또 다른 팁은 작고 간결한 기능을 개발하는 경우 인터페이스를 사용하는 것입니다. 큰 기능 단위를 디자인하는 경우 추상 클래스를 사용하십시오.

이것이 일부 사람들에게 문제가 해결되기를 바랍니다!

또한 더 나은 예를 생각할 수 있거나 지적하고 싶다면 아래 의견에 그렇게하십시오!


한 가지 중요한 차이점은 단지 상속 할 수 있다는 것입니다 하나의 기본 클래스,하지만 당신은 구현할 수있는 많은 인터페이스를. 따라서 다른 기본 클래스도 상속 할 필요가 없다고 절대적으로 확신 하는 경우에만 기본 클래스를 사용하고 싶습니다 . 또한 인터페이스가 커지면 독립적 인 기능을 정의하는 몇 개의 논리적 조각으로 나누기 시작해야합니다. 클래스가 모든 기능을 구현할 수 없다는 규칙이 없기 때문입니다 (또는 다른 그룹화하기 위해 모두 상속하는 인터페이스).


객체 지향 프로그래밍에 대해 처음 배우기 시작했을 때 상속을 사용하여 공통 동작을 공유하는 쉽고 일반적인 실수를 저질렀습니다. 해당 동작이 객체의 본질에 필수적이지 않은 경우에도 마찬가지였습니다.

많은이 특정 문제에 사용 된 예에 대한 자세한 빌드로, 거기에 많은 나는이 일반적인 동작을 제공 Petable 클래스를 가지고있다 할 수 있도록, 다양한 클래스는 상속 - 여자 친구, 자동차, 퍼지 담요 ... - petable 것들의는 그것에서.

그러나 귀여워하는 것은 이러한 물건의 본질의 일부가 아닙니다. 그들의 본성에 필수적인 훨씬 더 중요한 개념이 있습니다 -여자 친구는 사람, 차는 육상 차량, 고양이는 포유류 ...

동작은 먼저 인터페이스 (클래스의 기본 인터페이스 포함)에 할당되어야하며 (a) 더 큰 클래스의 하위 집합 인 대규모 클래스 그룹에 공통적 인 경우에만 기본 클래스로 승격되어야합니다. "고양이"와 "사람"은 "포유류"의 하위 집합입니다.

문제는 처음에했던 것보다 객체 지향 디자인을 충분히 잘 이해 한 후에는 일반적으로 생각조차하지 않고 자동으로이 작업을 수행한다는 것입니다. 따라서 "추상 클래스가 아닌 인터페이스에 대한 코드"라는 말의 진실은 너무나도 명백 해져서 누군가가 그것을 말하고 다른 의미를 읽으려고 시도 할 것이라고 믿기가 어렵습니다.

내가 추가 할 또 다른 점은 클래스가 순수하게 추상적 인 경우-비 추상적이거나 상속되지 않은 멤버 또는 자식, 부모 또는 클라이언트에 노출 된 메서드가없는 경우-클래스가 왜 클래스입니까? 어떤 경우에는 인터페이스로, 다른 경우에는 Null로 대체 될 수 있습니다.


추상 클래스보다 인터페이스 선호

이론적 근거, 고려해야 할 주요 사항 [여기에 이미 언급 된 두 가지]는 다음과 같습니다.

  • 클래스는 여러 인터페이스를 구현할 수 있으므로 인터페이스가 더 유연합니다. Java에는 다중 상속이 없으므로 추상 클래스를 사용하면 사용자가 다른 클래스 계층을 사용할 수 없습니다. 일반적으로 기본 구현이나 상태가없는 경우 인터페이스를 선호합니다. Java 컬렉션은 이에 대한 좋은 예를 제공합니다 (Map, Set 등).
  • 추상 클래스는 더 나은 순방향 호환성을 허용한다는 장점이 있습니다. 클라이언트가 인터페이스를 사용하면 변경할 수 없습니다. 추상 클래스를 사용하는 경우 기존 코드를 손상시키지 않고 여전히 동작을 추가 할 수 있습니다. 호환성이 중요한 경우 추상 클래스 사용을 고려하십시오.
  • 기본 구현이나 내부 상태가 있더라도 인터페이스와 추상 구현을 제공하는 것이 좋습니다. 이것은 고객에게 도움이되지만 원하는 경우 더 큰 자유를 허용합니다 [1].
    물론이 주제는 다른 곳에서 길게 논의되었습니다 [2,3].

[1] 물론 더 많은 코드를 추가하지만 간결함이 주요 관심사라면 처음부터 Java를 피해야 할 것입니다!

[2] Joshua Bloch, Effective Java, 항목 16-18.

[3] http://www.codeproject.com/KB/ar ...


일반적인 구현을 위해 추상 클래스를 사용하는 것에 대한 이전 의견은 확실히 표시됩니다. 아직 언급하지 않은 한 가지 이점은 인터페이스를 사용하면 단위 테스트 목적으로 모의 객체를 구현하는 것이 훨씬 쉬워진다는 것입니다. Jason Cohen이 설명한대로 IPet 및 PetBase를 정의하면 실제 데이터베이스의 오버 헤드없이 다양한 데이터 조건을 쉽게 조롱 할 수 있습니다 (실제 테스트시기를 결정할 때까지).


그것이 의미하는 바를 모르고이 경우에 적용된다는 것을 알지 못하는 한 기본 클래스를 사용하지 마십시오. 적용되는 경우 사용하고 그렇지 않은 경우 인터페이스를 사용하십시오. 그러나 작은 인터페이스에 대한 대답에 유의하십시오.

공개 상속은 OOD에서 과도하게 사용되며 대부분의 개발자가 인식하거나 기꺼이 수행하려는 것보다 훨씬 더 많이 표현합니다. Liskov 대체 원칙 참조

간단히 말해서 A가 B 인 경우 A는 노출하는 모든 메소드에 대해 B보다 더 이상 필요하지 않고 B보다 적게 전달합니다.


개념적으로 인터페이스는 객체가 제공 할 일련의 메서드를 공식 및 반 공식적으로 정의하는 데 사용됩니다. 공식적으로는 메소드 이름 및 서명 세트를 의미하고 반 공식적으로는 해당 메소드와 관련된 사람이 읽을 수있는 문서를 의미합니다. 인터페이스는 API에 대한 설명 일 뿐이며 (API는 Application Programmer Interface를 나타냄) 구현을 포함 할 수 없으며 인터페이스를 사용하거나 실행할 수 없습니다. 객체와 상호 작용하는 방법에 대한 계약 만 명시합니다.

클래스는 구현을 제공하며 0 개, 하나 이상의 인터페이스를 구현한다고 선언 할 수 있습니다. 클래스가 상속되는 경우 규칙은 클래스 이름 앞에 "Base"를 붙이는 것입니다.

There is a distinction between a Base Class and an Abstract Base Classes (ABC). ABCs mix interface and implementation together. Abstract outside of computer programming means "summary", that is "Abstract == Interface". An Abstract Base Class can then describe both an interface, as well as an empty, partial or complete implementation that is intended to be inherited.

인터페이스와 추상 기본 클래스를 사용해야하는 경우와 클래스 만 사용해야하는 경우에 대한 의견은 개발중인 항목과 개발중인 언어에 따라 크게 달라질 것입니다. 인터페이스는 종종 Java 또는 C #과 같은 정적으로 형식화 된 언어에만 연결되지만 동적 유형 언어는 인터페이스 및 추상 기본 클래스도 가질 수 있습니다. 예를 들어 Python에서는 인터페이스를 구현 한다고 선언 하는 Class와 Class의 인스턴스이며 해당 인터페이스 제공 한다고 하는 객체 사이의 구분이 명확 해집니다 . 동적 언어에서는 동일한 클래스의 인스턴스 인 두 개체가 완전히 다른 것을 제공한다고 선언 할 수 있습니다.인터페이스. 파이썬에서 이것은 객체 속성에 대해서만 가능하지만 메서드는 클래스의 모든 객체간에 공유 상태입니다. 그러나 Ruby에서 객체는 인스턴스 별 메서드를 가질 수 있으므로 동일한 클래스의 두 객체 사이의 인터페이스가 프로그래머가 원하는만큼 다를 수 있습니다 (그러나 Ruby에는 인터페이스를 명시 적으로 선언하는 방법이 없습니다).

동적 언어에서 객체에 대한 인터페이스는 객체를 검사하고 제공하는 메소드 (Look Before You Leap)를 요청하거나 객체에서 원하는 인터페이스를 사용하려고 시도하고 객체가 예외 인 경우 예외를 포착하여 암시 적으로 가정하는 경우가 많습니다. 해당 인터페이스를 제공하지 않습니다 (허가보다 용서를 구하기가 더 쉽습니다). 이로 인해 두 인터페이스가 동일한 메서드 이름을 갖지만 의미가 다른 "거짓 긍정"이 발생할 수 있지만 가능한 모든 사용을 예상하기 위해 미리 과도하게 지정할 필요가 없기 때문에 코드가 더 유연하다는 단점이 있습니다. 코드의.


염두에 두어야 할 또 다른 옵션은 "has-a"관계를 사용하는 것입니다. 일명 "is-ample in terms of"또는 "composition"입니다. 때때로 이것은 "is-a"상속을 사용하는 것보다 사물을 구조화하는 더 깨끗하고 유연한 방법입니다.

Dog와 Cat이 모두 Pet을 "갖고있다"고 말하는 것은 논리적으로 그다지 말이되지 않을 수 있지만 일반적인 다중 상속 함정을 피합니다.

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

예,이 예제는 이러한 방식으로 작업을 수행하는 데 관련된 코드 중복과 우아함이 부족함을 보여줍니다. 그러나 이것은 또한 Dog와 Cat이 Pet 클래스에서 분리 된 상태를 유지하는 데 도움이되며 (Dog 및 Cat은 Pet의 개인 멤버에 액세스 할 수 없음) Dog와 Cat이 다른 것을 상속 할 수있는 여지를 남깁니다. -아마도 포유류 클래스.

개인 액세스가 필요하지 않고 일반적인 애완 동물 참조 / 포인터를 사용하여 개와 고양이를 참조 할 필요가없는 경우 구성이 선호됩니다. 인터페이스는 일반적인 참조 기능을 제공하고 코드의 자세한 정도를 줄이는 데 도움이 될 수 있지만 제대로 구성되지 않은 경우 내용을 난독화할 수도 있습니다. 상속은 개인 멤버 액세스가 필요할 때 유용하며이를 사용하면 Dog 및 Cat 클래스를 Pet 클래스에 고도로 결합하는 데 많은 비용이 듭니다.

상속, 구성 및 인터페이스 사이에는 항상 올바른 방법이 없으며 세 가지 옵션을 모두 조화롭게 사용할 수있는 방법을 고려하는 것이 좋습니다. 세 가지 중 상속은 일반적으로 가장 적게 사용되어야하는 옵션입니다.


귀하의 요구 사항에 따라 다릅니다. IPet이 충분히 간단하다면 그것을 구현하는 것을 선호합니다. 그렇지 않으면 PetBase가 복제하고 싶지 않은 수많은 기능을 구현한다면 그 기능을 사용하십시오.

기본 클래스 구현의 단점은 기존 메서드에 대한 요구 사항 override(또는 new)입니다. 이것은 그것들을 가상 메소드로 만든다. 즉, 객체 인스턴스를 사용하는 방법에주의해야 함을 의미한다.

마지막으로, .NET의 단일 상속은 나를 죽입니다. 순진한 예 : 사용자 컨트롤을 만들고 있으므로 UserControl. 하지만 이제 PetBase. 이렇게하면 PetBase대신 클래스 구성원 을 만들기 위해 재구성해야합니다 .


@Joel : 일부 언어 (예 : C ++)는 다중 상속을 허용합니다.


나는 보통 하나가 필요할 때까지 구현하지 않습니다. 나는 추상 클래스보다 인터페이스를 선호합니다. 상속 클래스 중 일부에 공통적 인 동작이 있으면이를 위로 이동하여 추상 기본 클래스를 만듭니다. 본질적으로 동일한 목적을 서버로 사용하고 두 가지를 모두 갖는 것은 솔루션이 과도하게 설계되었다는 나쁜 코드 냄새 (imho)이기 때문에 둘 다에 대한 필요성을 보지 못합니다.


C #과 관련하여 어떤 의미에서 인터페이스와 추상 클래스는 서로 바꿔서 사용할 수 있습니다. 그러나 차이점은 다음과 같습니다. i) 인터페이스는 코드를 구현할 수 없습니다. ii)이 때문에 인터페이스는 스택을 하위 클래스로 더 이상 호출 할 수 없습니다. iii) 추상 클래스 만 클래스에서 상속 될 수있는 반면 여러 인터페이스가 클래스에서 구현 될 수 있습니다.


def에 의해 인터페이스는 다른 코드와 통신 할 수있는 레이어를 제공합니다. 클래스의 모든 공용 속성 및 메서드는 기본적으로 암시 적 인터페이스를 구현합니다. 인터페이스를 역할로 정의 할 수도 있습니다. 어떤 클래스가 해당 역할을 수행해야 할 때마다이를 구현하는 클래스에 따라 다른 형태의 구현을 제공해야합니다. 따라서 인터페이스에 대해 이야기 할 때 다형성에 대해 이야기하고 기본 클래스에 대해 이야기 할 때 상속에 대해 이야기하고 있습니다. 죄송합니다의 두 가지 개념 !!!


Interface> Abstract> Concrete 패턴이 다음 사용 사례에서 작동 함을 발견했습니다.

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

추상 클래스는 구체적인 클래스의 기본 공유 속성을 정의하지만 인터페이스를 적용합니다. 예를 들면 :

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

이제 모든 포유류는 머리카락과 젖꼭지를 가지고 있기 때문에 (AFAIK, 저는 동물학자가 아닙니다), 우리는 이것을 추상 기본 클래스에 넣을 수 있습니다.

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

그리고 구체적인 클래스는 단지 그들이 똑바로 걷는다는 것을 정의합니다.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

이 디자인은 구체적인 클래스가 많고 인터페이스에 프로그래밍하기 위해 상용구를 유지하고 싶지 않을 때 유용합니다. 인터페이스에 새 메서드가 추가되면 결과 클래스가 모두 중단되므로 인터페이스 접근 방식의 이점을 계속 얻을 수 있습니다.

이 경우 초록은 구체적 일 수도 있습니다. 그러나 추상 지정은이 패턴이 사용되고 있음을 강조하는 데 도움이됩니다.


기본 클래스의 상속자는 "is a"관계를 가져야합니다. 인터페이스는 "구현"관계를 나타냅니다. 따라서 상속자가 관계를 유지할 때만 기본 클래스를 사용하십시오.

참고 URL : https://stackoverflow.com/questions/56867/interface-vs-base-class

반응형