문제

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

실제로 메소드의 기본 구현을 정의하고 싶지 않은 경우 항상 인터페이스여야 합니까?

개와 고양이 수업이 있다면.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는 다음과 같이 말했습니다. 효과적인 Java 2D:

추상 클래스보다 인터페이스를 선호하세요

몇 가지 주요 사항:

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

  • 인터페이스는 믹스인을 정의하는 데 이상적입니다..느슨하게 말하면, Mixin은 클래스가 "기본 유형"외에도 선택적 행동을 제공한다고 선언하기 위해 클래스가 구현할 수있는 유형입니다.예를 들어, 비교할 수있는 믹스 인 인터페이스는 클래스가 다른 상호 비슷한 객체와 관련하여 인스턴스가 순서라고 선언 할 수있게하는 믹스 인 인터페이스입니다.

  • 인터페이스는 비 계층 적 유형 프레임 워크를 구성 할 수 있습니다.유형 계층은 몇 가지를 구성하는 데 적합하지만 다른 것들은 엄격한 계층에 깔끔하게 떨어지지 않습니다.

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

또한, 추상 골격 구현 클래스를 제공하여 내보내는 각각의 사소한 인터페이스와 함께 이동하여 인터페이스와 추상 클래스의 미덕을 결합 할 수 있습니다.

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

추신.:책을 구입하세요.훨씬 더 자세합니다.

현대적인 스타일은 IPet을 정의하는 것입니다. 그리고 PetBase.

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

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

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

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

인터페이스, 반면에 표현하다 추가 기능 수업의.나는 이것을 '이다' 관계라고 부르고 싶습니다.Foo 일회용이다" 그러므로 IDisposable C#의 인터페이스.

인터페이스

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

기본 클래스

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

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

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

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

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

.NET Framework의 세 가지 버전을 진행하면서 저는 우리 팀의 꽤 많은 개발자들과 이 지침에 대해 이야기했습니다.처음에 지침에 동의하지 않은 사람들을 포함하여 많은 사람들이 일부 API를 인터페이스로 출시한 것을 후회한다고 말했습니다.누군가가 수업을 내놓은 것을 후회한 경우는 단 한 번도 들어본 적이 없습니다.

즉, 인터페이스를 위한 장소가 확실히 있다는 것입니다.일반적인 지침으로 인터페이스를 구현하는 방법의 예로서 다른 것이 없다면 항상 인터페이스의 추상 기본 클래스 구현을 제공하십시오.가장 좋은 경우에는 기본 클래스가 많은 작업을 줄여줄 것입니다.

후안,

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

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

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

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

따라서 당신이 나처럼 생각한다면 분명히 Cat과 Dog는 IPettable이라고 말할 것입니다.둘 다 일치하는 특성입니다.

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

모든 동물 클래스를 모아 방주 컨테이너에 넣고 싶다고 가정해 보겠습니다.

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

그들은 전혀 서로 연결될 필요가 있습니까?둘 다 IPettable이라는 것을 아는 것만으로도 충분합니까?

나는 실제로 하나의 클래스만 필요할 때 전체 클래스 계층 구조를 파생시키고 싶은 욕구를 자주 느낍니다.언젠가는 필요할지도 모른다는 예상으로 그렇게 하지만 대개는 절대 그렇게 하지 않습니다.그렇게 하더라도 대개는 그것을 고치기 위해 많은 일을 해야 한다는 것을 알게 됩니다.내가 만드는 첫 번째 클래스는 개가 아니고 운이 좋지도 않고 오리너구리이기 때문입니다.이제 내 전체 클래스 계층 구조는 기괴한 사례를 기반으로 하며 낭비되는 코드가 많이 있습니다.

또한 모든 고양이가 IPettable이 아닌 경우도 있습니다(예: 털이 없는 고양이).이제 해당 인터페이스를 적합한 모든 파생 클래스로 이동할 수 있습니다.갑자기 Cats가 더 이상 PettableBase에서 파생되지 않는다는 획기적인 변경 사항이 훨씬 적다는 것을 알게 될 것입니다.

인터페이스와 기본 클래스의 기본적이고 간단한 정의는 다음과 같습니다.

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

건배

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

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

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

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

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

이 내용에 잘 설명되어 있습니다 자바 월드 기사

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

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

나는 다른 것의 기초로 사용하는 추상 클래스를 사용합니다.

다음은 위 기사에서 발췌한 내용입니다. JavaWorld.com 기사, 저자 Tony Sintes, 2001년 4월 20일


인터페이스 대추상 수업

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

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

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

클래스 대상호 작용

어떤 사람들은 인터페이스 측면에서 모든 클래스를 정의해야 한다고 말하지만, 제 생각에는 권장사항이 좀 극단적인 것 같습니다.나는 내 디자인의 내용이 자주 변경되는 것을 볼 때 인터페이스를 사용합니다.

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

전략 패턴을 사용하면 객체 뒤에 알고리즘을 간단히 캡슐화할 수 있습니다.그렇게 하면 언제든지 새로운 미디어 플러그인을 제공할 수 있습니다.플러그인 클래스 MediaStrategy를 호출해 보겠습니다.해당 객체에는 다음과 같은 한 가지 메서드가 있습니다.playStream(스트림들).따라서 새 알고리즘을 추가하려면 간단히 알고리즘 클래스를 확장하면 됩니다.이제 프로그램이 새로운 미디어 유형을 만나면 단순히 스트림 재생을 미디어 전략에 위임합니다.물론 필요한 알고리즘 전략을 적절하게 인스턴스화하려면 몇 가지 배관 작업이 필요합니다.

이곳은 인터페이스를 사용하기에 좋은 장소입니다.우리는 디자인에서 변경될 위치를 명확하게 나타내는 전략 패턴을 사용했습니다.따라서 전략을 인터페이스로 정의해야 합니다.객체가 특정 유형을 갖기를 원할 때는 일반적으로 상속보다 인터페이스를 선호해야 합니다.이 경우에는 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 (User Interface) 컨트롤이며 일부는 XML 웹 서비스입니다).
  3. 집계는 적절하지 않거나 실행 가능하지 않습니다.다른 모든 상황에서 클래스 상속은 더 나은 모델입니다.

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

C#은 지난 14년 동안 성숙하고 발전해 온 훌륭한 언어입니다.성숙한 언어는 우리가 사용할 수 있는 수많은 언어 기능을 제공하기 때문에 이는 우리 개발자에게 좋습니다.

그러나 권력이 많으면 책임도 커지게 됩니다.이러한 기능 중 일부는 잘못 사용될 수 있으며 때로는 다른 기능보다 한 기능을 선택하는 이유를 이해하기 어렵습니다.수년에 걸쳐 많은 개발자들이 어려움을 겪는 것을 본 기능은 언제 인터페이스를 사용할지 또는 추상 클래스를 사용할지 선택하는 것입니다.두 가지 모두 장점과 단점이 있으며 각각을 사용할 적절한 시간과 장소가 있습니다.하지만 어떻게 결정하나요???

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

이것은 모두 훌륭하고 훌륭하며 코드를 훌륭하게 재사용할 수 있으며 소프트웨어 개발의 DRY(Don'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를 무료로 상속받을 수 있습니다.

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

당신은 추상 클래스 대신 인터페이스를 선택하거나 그 반대로 인터페이스를 선택하려는 이유를 이해하고 싶기 때문에 여기에 왔습니다.추상 클래스 대신 인터페이스를 선택하려는 한 가지 이유는 기본 구현이 없거나 이를 방지하고 싶을 때입니다.이는 일반적으로 인터페이스를 구현하는 유형이 "is a" 관계와 관련이 없기 때문입니다.실제로 각 유형이 무언가를 하거나 무언가를 가질 수 있는 "능력"이 있다는 사실을 제외하고는 전혀 관련될 필요가 없습니다.

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

// 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) 등)을 수행하기 위해 객체를 호출하는 여러 메서드를 작성할 필요가 없도록 코드를 DRY 상태로 유지하는 데 도움이 됩니다.

여기서 인터페이스를 사용하면 호출 메서드가 어떤 유형인지 또는 동작이 어떻게 구현되는지 걱정할 필요가 없습니다.주어진 인터페이스에 각 객체가 Swim 메서드를 구현해야 하므로 자체 코드에서 이를 호출하고 Swim 메서드의 동작이 자체 클래스 내에서 처리되도록 허용해야 한다는 점만 알고 있습니다.

요약:

따라서 나의 주요 경험 법칙은 클래스 계층 구조에 대한 "기본" 기능을 구현하려는 경우 또는 작업 중인 클래스나 유형이 "is a" 관계를 공유하려는 경우 추상 클래스를 사용하는 것입니다(예:푸들은 "개" 유형의 개입니다).

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

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

인터페이스를 사용하여 ISP(인터페이스 분리 원칙)를 준수할 때도 기억하세요.ISP에서는 어떤 클라이언트도 자신이 사용하지 않는 방법에 의존하도록 강요해서는 안 된다고 명시합니다.이러한 이유로 인터페이스는 특정 작업에 초점을 맞춰야 하며 일반적으로 매우 작습니다(예:IDisposable, IComparable ).

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

이것이 일부 사람들의 문제를 해결하기를 바랍니다!

또한 더 좋은 예가 생각나거나 지적하고 싶은 점이 있으면 아래 댓글에 적어주세요!

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

객체지향 프로그래밍을 처음 배우기 시작했을 때, 나는 상속을 사용하여 공통 동작을 공유하는 쉽고 아마도 흔한 실수를 저질렀습니다. 심지어 그 동작이 객체의 특성에 필수적이지 않은 경우에도 마찬가지였습니다.

이 특정 질문에 많이 사용된 예를 더 발전시키기 위해 다음이 있습니다. 많이 여자친구, 자동차, 폭신한 담요 등 애완동물 같은 것들...- 따라서 이러한 공통 동작을 제공하는 Petable 클래스와 이를 상속받는 다양한 클래스가 있었을 것입니다.

그러나 애완동물이 되는 것은 이러한 물체의 성격의 일부가 아닙니다.훨씬 더 중요한 개념이 있습니다. ~이다 그들의 본성에 필수적인 것입니다. 여자친구는 사람이고, 차는 육상 차량이고, 고양이는 포유류입니다...

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

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

내가 추가하고 싶은 또 다른 점은 수업이 전혀 abstract - 추상이 아니고 상속되지 않은 멤버나 메서드가 자식, 부모 또는 클라이언트에 노출되지 않은 경우 - 그렇다면 클래스인 이유는 무엇입니까?어떤 경우에는 인터페이스로, 다른 경우에는 Null로 대체될 수 있습니다.

추상 클래스보다 인터페이스를 선호하세요

이론적 근거, [여기에서 이미 언급 한 두 가지]를 고려해야 할 요점은 다음과 같습니다.

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

[1] 물론 더 많은 코드가 추가되지만, 간결함이 주요 관심사라면 애초에 Java를 피했어야 했을 것입니다!

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

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

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

기본 클래스의 의미와 이 경우 적용되는 내용을 알지 않는 한 기본 클래스를 사용하지 마세요.적용되면 사용하고, 그렇지 않으면 인터페이스를 사용하십시오.그러나 작은 인터페이스에 대한 답변을 참고하십시오.

Public Inheritance는 OOD에서 과도하게 사용되며 대부분의 개발자가 인식하거나 준수하려는 것보다 훨씬 더 많은 것을 표현합니다.참조 Liskov 대체성 원칙

간단히 말해서, A가 B인 경우 A는 노출되는 모든 메서드에 대해 B 이상이 필요하지 않으며 B 이상을 제공합니다.

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

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

기본 클래스와 추상 기본 클래스(ABC) 간에는 차이가 있습니다.ABC는 인터페이스와 구현을 함께 혼합합니다.컴퓨터 프로그래밍 밖의 추상은 "요약", 즉 "추상 == 인터페이스"를 의미합니다.그런 다음 추상 기본 클래스는 인터페이스뿐만 아니라 상속하려는 빈, 부분 또는 전체 구현을 모두 설명할 수 있습니다.

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

동적 언어에서 객체에 대한 인터페이스는 종종 암시적으로 가정됩니다. 객체를 검사하고 객체가 제공하는 메서드가 무엇인지 묻는 방식(Look Before You Leap) 또는 바람직하게는 단순히 객체에서 원하는 인터페이스를 사용하려고 시도하고 객체가 예외를 포착하는 방식입니다. 해당 인터페이스를 제공하지 않습니다(허가보다 용서를 구하는 것이 더 쉽습니다).이로 인해 두 인터페이스의 메서드 이름이 같지만 의미가 다른 "오탐지"가 발생할 수 있습니다. 그러나 가능한 모든 용도를 예상하기 위해 미리 과도하게 지정할 필요가 없기 때문에 코드가 더 유연하다는 단점이 있습니다. 당신의 코드.

명심해야 할 또 다른 옵션은 "has-a"관계, 일명 "이"또는 "구성"의 관점에서 구현되는 것입니다. 때때로 이것은 "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 대신에 반원.

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

나는 보통 필요할 때까지 구현하지 않습니다.나는 좀 더 유연성을 제공하기 때문에 추상 클래스보다 인터페이스를 선호합니다.일부 상속 클래스에 공통된 동작이 있는 경우 이를 위로 이동하여 추상 기본 클래스를 만듭니다.본질적으로 동일한 목적을 제공하고 둘 다 사용하면 솔루션이 과도하게 엔지니어링되어 나쁜 코드 냄새가 나기 때문에 둘 다 필요하지 않습니다.

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

기본적으로 인터페이스는 다른 코드와 통신하기 위한 레이어를 제공합니다.클래스의 모든 공용 속성과 메서드는 기본적으로 암시적 인터페이스를 구현합니다.인터페이스를 역할로 정의할 수도 있습니다. 클래스가 해당 역할을 수행해야 할 때 인터페이스를 구현해야 하며 이를 구현하는 클래스에 따라 다른 형태의 구현을 제공해야 합니다.따라서 인터페이스에 대해 이야기할 때는 다형성에 대해 이야기하고 기본 클래스에 대해 이야기할 때는 상속에 대해 이야기하는 것입니다.앗 두 가지 컨셉!!!

인터페이스 > 추상 > 콘크리트 패턴이 다음 사용 사례에서 작동하는 것으로 나타났습니다.

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top