인터페이스는 언제 사용해야합니까? 기본 클래스는 언제 사용해야합니까?
실제로 메소드의 기본 구현을 정의하고 싶지 않으면 항상 인터페이스 여야합니까?
개와 고양이 수업이 있으면. PetBase 대신 IPet를 구현해야하는 이유는 무엇입니까? 내가 애완 동물 기준으로 애완 동물에 배치 할 수 있기 때문에 내가 ISheds 또는 IBarks (IMakesNoise?)에 대한 인터페이스를 가지고 이해할 수 있지만, 나는 일반 애완 동물에 사용할 이해가 안돼.
개와 고양이 클래스의 예를 들고 C #을 사용하여 설명해 보겠습니다.
개와 고양이는 모두 동물, 특히 포유 동물 (동물은 너무 일반적 임)입니다. 두 가지 모두에 대해 추상 클래스 인 포유류가 있다고 가정 해 보겠습니다.
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);
}
위의 계약에 대한 구현은 고양이와 개간에 동일하지 않습니다. 상속받을 추상 클래스에 구현을 넣는 것은 나쁜 생각입니다.
개와 고양이 정의는 이제 다음과 같이 보입니다.
public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable
이론적으로 상위 기본 클래스에서 재정의 할 수 있지만 기본적으로 인터페이스를 사용하면 상속없이 클래스에 필요한 것만 추가 할 수 있습니다.
결과적으로 보통 하나의 추상 클래스에서만 상속 할 수 있기 때문에 (대부분 정적으로 입력 된 OO 언어에서 예외는 C ++을 포함하지만) 다중 인터페이스를 구현할 수 있기 때문에 엄격하게 객체를 구성 할 수 있습니다필요에 따라기초.
조쉬 블로흐는효과적인 Java 2d:
몇 가지 주요 포인트 :
기존 클래스를 쉽게 개조하여 새로운 클래스를 구현할 수 있습니다. 인터페이스. 당신이해야 할 일은 그들이 아직하지 않으면 필요한 방법 존재하고 implements 절을 다음에 추가하십시오. 클래스 선언
인터페이스는 믹스 인을 정의하는 데 이상적입니다.. 느슨하게 말해서, a mixin은 클래스가 할 수있는 타입이다. 그것의 "기본 유형 "을 제공한다고 선언합니다. 일부 선택적 동작. 예를 들어, 비교할 수있는 mixin 인터페이스는 수업을 통해 인스턴스는 다른 상호 비교 가능한 객체.
인터페이스는 비 계층 적 유형의 생성을 허용합니다. 프레임 워크. 유형 계층 구조는 다음과 같습니다. 어떤 것을 조직하는 데는 좋지만 다른 것들은 깔끔하게 떨어지지 않습니다. 엄격한 계층 구조.
인터페이스를 통해 안전하고 강력한 기능 향상~을 통해 클래스 관용구. 사용하는 경우 유형을 정의하는 추상 클래스, 추가하고 싶은 프로그래머에게 맡겨라. 대안이없는 기능성 상속을 사용합니다.
또한, 덕을 결합 할 수 있습니다. 에 의한 인터페이스 및 추상 클래스 추상적 인 골격을 제공하다 각 구현 클래스 당신이 내보내는 사소한 인터페이스.
반면에 인터페이스는 진화가 매우 어렵습니다. 인터페이스에 메소드를 추가하면 모든 구현이 중단됩니다.
추신 : 책을 사세요. 훨씬 더 자세합니다.
현대적인 스타일은 IPet를 정의하는 것입니다.과PetBase.
인터페이스의 장점은 다른 코드가 다른 실행 코드와 아무 관계없이 사용할 수 있다는 것입니다. 완전히 "깨끗하게"하십시오. 또한 인터페이스를 혼합 할 수 있습니다.
하지만 기본 클래스는 간단한 구현과 일반적인 유틸리티에 유용합니다. 따라서 시간과 코드를 절약하기 위해 추상 기본 클래스도 제공하십시오.
인터페이스와 기본 클래스는 서로 다른 두 가지 형태의 관계를 나타냅니다.
계승(기본 클래스)는 "is-a"관계를 나타냅니다. 예 : 개 또는 고양이 "is-a"애완 동물. 이 관계는 항상 (단일)목적수업과 함께 ("단일 책임 원칙").
인터페이스, 반면에,추가 기능클래스의. 나는 그것을 "관계"라고 부르겠다.Foo
일회용 "이므로IDisposable
C #에서 인터페이스.
일반적으로 추상 클래스보다 인터페이스를 선호해야합니다. 추상 클래스를 사용하는 한 가지 이유는 구체적인 클래스 사이에 공통된 구현이있는 경우입니다. 물론 인터페이스 (IPet)를 선언하고 추상 클래스 (PetBase)에서 해당 인터페이스를 구현해야합니다. 작고 뚜렷한 인터페이스를 사용하여 배수를 사용하여 유연성을 향상시킬 수 있습니다. 인터페이스를 사용하면 경계를 넘는 유형의 최대한의 유연성과 이식성이 가능합니다. 경계를 넘어서 참조를 전달할 때는 항상 구체적인 유형이 아닌 인터페이스를 전달하십시오. 이를 통해 수신 측에서 구체적인 구현을 결정하고 최대한의 유연성을 제공 할 수 있습니다. 이것은 TDD / BDD 방식으로 프로그래밍 할 때 절대적으로 사실입니다.
The Gang of Four는 "상속은 상위 구현의 세부 정보에 하위 클래스를 제공하기 때문에 상속으로 캡슐화가 중단된다고 종종 말합니다. 나는 이것이 사실이라고 믿는다.
이것은 .NET에 한정적이지만 Framework Design Guidelines 책에서는 일반적인 클래스에서는 진화하는 프레임 워크에서 더 많은 유연성을 제공한다고 주장합니다. 인터페이스가 배송되면 해당 인터페이스를 사용하는 코드를 위반하지 않고 인터페이스를 변경할 기회가 없습니다. 그러나 클래스를 사용하면 클래스를 수정하고 해당 클래스에 링크 된 코드를 중단 할 수 있습니다. 새로운 기능을 추가하는 등 올바른 수정을하면 코드를 확장하고 전개 할 수 있습니다.
Krzysztof Cwalina는 81쪽에 다음과 같이 말했습니다 :
세 가지 버전의 .NET Framework에서 우리 팀의 상당수 개발자와이 지침에 대해 이야기했습니다. 처음에는 가이드 라인에 동의하지 않은 사람들을 포함하여 많은 사람들이 인터페이스로 API를 선적 한 것을 후회한다고 말했습니다. 나는 누군가가 그들이 수업을 출품 한 것을 후회 한 경우조차도 듣지 못했다.
거기에는 분명히 인터페이스를위한 장소가 있다고합니다. 일반적인 지침으로서, 인터페이스를 구현하는 방법의 예로 다른 것을 위해 필요하다면 항상 인터페이스의 추상 기본 클래스 구현을 제공하십시오. 가장 좋은 경우에는 기본 클래스가 많은 작업을 줄일 수 있습니다.
후안,
저는 인터페이스를 클래스를 특징 짓는 방법으로 생각하고 싶습니다. YorkshireTerrier와 같은 특정 개종 클래스는 부모 Dog 클래스의 자손이지만 IFurry, IStubby 및 IYippieDog도 구현됩니다. 그래서 클래스는 클래스가 무엇인지 정의하지만 인터페이스는 그것에 대해 우리에게 알려줍니다.
이것의 장점은 예를 들어, 모든 IYippieDog를 모아서 Ocean 컬렉션에 던지게 할 수 있다는 것입니다. 이제는 특정 개체 집합에 접근하여 클래스를 너무 가깝게 검사하지 않고보고있는 기준을 충족하는 개체를 찾을 수 있습니다.
인터페이스가 실제로 클래스의 공용 동작 하위 집합을 정의해야한다는 것을 알게되었습니다. 그것이 구현하는 모든 클래스에 대한 모든 public 동작을 정의하면 일반적으로 존재할 필요가 없습니다. 그들은 나에게 유용한 어떤 것도 말하지 않는다.
이 생각은 모든 클래스에는 인터페이스가 있어야하며 인터페이스에 코딩해야한다는 생각에 어긋납니다. 괜찮습니다. 그러나 클래스에 대한 일대일 인터페이스가 많아지고 혼란 스럽습니다. 나는 그 아이디어가 실제로는 아무 것도 할 필요가 없다는 것을 이해하며 이제는 물건을 쉽게 교환 할 수 있습니다. 그러나 나는 거의하지 않는다는 것을 안다. 대부분의 경우 기존 클래스를 수정하고 클래스의 공개 인터페이스가 변경되어야하는 경우 똑같은 문제가 발생합니다. 단 두 곳에서 변경해야합니다.
그래서 나처럼 생각한다면 고양이와 개가 IPettable이라고 분명히 말할 것입니다. 둘 모두와 일치하는 것은 특성입니다.
이것의 다른 부분은 그들이 동일한 기본 클래스를 가져야 하는가? 문제는 그들이 똑같은 것으로 폭넓게 다뤄야 할 필요가 있다는 것입니다. 틀림없이 그들은 둘 다 동물이지만 우리가 함께 사용하는 방법에 적합합니다.
모든 동물 수업을 모아서 방주에 넣고 싶습니다.
아니면 포유류가되어야합니까? 아마도 우리는 일종의 동물 간 착유 공장이 필요할까요?
그들은 심지어 전혀 연결되어 있어야합니까? 그들이 둘 다 IPettable이라는 것을 아는 것으로 충분합니까?
클래스를 하나만 필요로 할 때 전체 클래스 계층 구조를 파생시키려는 욕구를 자주 느낍니다. 나는 언젠가 그것을 필요로 할지도 모르고, 보통 나는 결코하지 않는다. 내가 할 때조차도, 나는 보통 그것을 해결하기 위해 많은 것을해야한다는 것을 알게된다. 왜냐하면 제가 만드는 첫 번째 수업은 개가 아니기 때문입니다. 나는 운이 좋았지 만, 오히려 오리너구리입니다. 이제 전체 클래스 계층 구조는 기괴한 경우를 기반으로하며 많은 낭비되는 코드가 있습니다.
또한 어떤 시점에서 모든 고양이가 IPettable 인 것은 아니라는 것을 알 수 있습니다. 이제 해당 인터페이스를 적합한 모든 파생 클래스로 이동할 수 있습니다. 갑작스런 고양이가 더 이상 PettableBase에서 파생되지 않는다는 사실을 깨닫지 못할 것입니다.
다음은 인터페이스와 기본 클래스의 기본적이고 간단한 정의입니다.
건배
가능할 때마다 상속 대신 컴포지션을 사용하는 것이 좋습니다. 인터페이스를 사용하지만 기본 구현을 위해 구성원 객체를 사용합니다. 그렇게하면 특정 방법으로 동작하도록 객체를 구성하는 팩토리를 정의 할 수 있습니다. 비헤이비어를 변경하려면 다른 유형의 하위 객체를 만드는 새 팩토리 메소드 (또는 추상 팩토리)를 만듭니다.
경우에 따라 변경 가능한 모든 동작이 도우미 객체에 정의되어 있으면 기본 객체에 인터페이스가 필요하지 않음을 알 수 있습니다.
따라서 IPet 또는 PetBase 대신 IFurBehavior 매개 변수가있는 Pet로 끝날 수도 있습니다. IFurBehavior 매개 변수는 PetFactory의 CreateDog () 메서드에 의해 설정됩니다. 이 매개 변수는 shed () 메소드에 대해 호출됩니다.
이렇게하면 코드가 훨씬 유연 해지고 간단한 객체의 대부분이 매우 기본적인 시스템 전반의 동작을 처리하게됩니다.
다중 상속 언어에서도이 패턴을 권장합니다.
이것으로 잘 설명했다.자바 월드 문서
개인적으로 나는 인터페이스를 사용하여 인터페이스를 정의하는 경향이 있습니다. 즉 인터페이스에 무언가를 액세스해야하는 방법을 지정하는 시스템 디자인 부분입니다.
하나 이상의 인터페이스를 구현하는 클래스를 갖게되는 것은 드문 일이 아닙니다.
다른 클래스의 기초로 사용하는 추상 클래스.
다음은 위에서 언급 한 기사에서 발췌 한 내용입니다.JavaWorld.com 기사, 저자 Tony Sintes, 04/20/01
인터페이스와 추상 클래스
인터페이스와 추상 클래스를 선택하는 것은 하나의 / 또는 하나의 명제가 아닙니다. 디자인을 변경해야하는 경우 인터페이스로 설정하십시오. 그러나 일부 기본 동작을 제공하는 추상 클래스가있을 수 있습니다. 추상 클래스는 응용 프로그램 프레임 워크 내부의 우수한 후보입니다.
추상 클래스를 사용하면 일부 비헤이비어를 정의 할 수 있습니다. 그들은 당신의 하위 클래스가 다른 것들을 제공하도록 강제합니다. 예를 들어 응용 프로그램 프레임 워크가있는 경우 추상 클래스는 이벤트 및 메시지 처리와 같은 기본 서비스를 제공 할 수 있습니다. 이러한 서비스를 통해 응용 프로그램을 응용 프로그램 프레임 워크에 연결할 수 있습니다. 그러나 응용 프로그램에서만 수행 할 수있는 응용 프로그램 특정 기능이 있습니다. 이러한 기능에는 종종 응용 프로그램에 따라 달라지는 시작 및 종료 작업이 포함될 수 있습니다. 따라서 그 동작 자체를 정의하는 대신 추상 추상 클래스는 추상 종료 및 시작 메소드를 선언 할 수 있습니다. 기본 클래스는 이러한 메서드가 필요하다는 것을 알고 있지만 추상 클래스는 클래스에서 해당 작업을 수행하는 방법을 모르는 것을 인정합니다. 그것은 행동을 시작해야한다는 것을 알고 있습니다. 시작할 시간이되면 추상 클래스가 시작 메소드를 호출 할 수 있습니다. 기본 클래스가이 메소드를 호출하면 Java 클래스가 정의한 메소드를 Java가 호출합니다.
많은 개발자들은 추상 메소드를 정의하는 클래스가 그 메소드를 호출 할 수 있다는 것을 잊어 버립니다. 추상 클래스는 계획 상속 계층을 만드는 훌륭한 방법입니다. 또한 클래스 계층 구조의 리프가 아닌 클래스에 적합합니다.
클래스 대 인터페이스
어떤 사람들은 인터페이스의 관점에서 모든 클래스를 정의해야한다고 말하지만, 나는 약간의 극단적 인 것으로 보인다. 디자인에서 무언가가 자주 바뀌는 것을 볼 때 인터페이스를 사용합니다.
예를 들어, 전략 패턴을 사용하면 새로운 알고리즘과 프로세스를 사용하는 객체를 변경하지 않고도 새 알고리즘과 프로세스를 프로그램으로 스왑 할 수 있습니다. 미디어 플레이어는 CD, MP3 및 WAV 파일을 재생하는 방법을 알고있을 수 있습니다. 물론 재생 알고리즘을 플레이어에 하드 코딩하고 싶지는 않습니다. 그러면 AVI와 같은 새로운 형식을 추가하기가 어려울 것입니다. 또한 코드에 쓸데없는 case 문이 깔려 있습니다. 그리고 모욕을 외상에 추가하려면 새 알고리즘을 추가 할 때마다 해당 사례 문을 업데이트해야합니다. 대체로 이것은 객체 지향적 인 프로그래밍 방식이 아닙니다.
전략 패턴을 사용하면 객체 뒤에 알고리즘을 캡슐화 할 수 있습니다. 그렇게하면 언제든지 새 미디어 플러그인을 제공 할 수 있습니다. 플러그인 클래스 인 MediaStrategy를 호출 해 봅시다. 이 객체에는 playStream (Stream s) 메서드가 있습니다. 그래서 새로운 알고리즘을 추가하기 위해서 간단히 알고리즘 클래스를 확장합니다. 이제 프로그램이 새로운 미디어 유형을 만났을 때 스트림의 재생을 미디어 전략에 위임합니다. 물론 필요로하는 알고리즘 전략을 올바르게 구현하려면 배관을 사용해야합니다.
이것은 인터페이스를 사용하기에 좋은 곳입니다. Strategy 패턴을 사용했습니다.이 패턴은 디자인에서 변경 될 위치를 명확하게 나타냅니다. 따라서 전략을 인터페이스로 정의해야합니다. 객체가 특정 유형을 원할 때 상속에 대한 인터페이스를 선호해야합니다. 이 경우에는 MediaStrategy입니다. 유형 식별을위한 상속에 의존하는 것은 위험합니다. 당신을 특정 상속 계층 구조로 고정시킵니다. Java는 다중 상속을 허용하지 않으므로 유용한 구현 또는 더 많은 유형 ID를 제공하는 무언가를 확장 할 수 없습니다.
또한 OO에서 휩쓸 리지 않도록 (블로그를 보아라.) 필요한 행동을 기반으로 항상 객체를 모델링합니다. 필요한 행동은 동물에 대한 일반적인 이름과 종일 경우에만 응용 프로그램을 디자인하는 경우 수백만 개 대신 이름 대신 속성이있는 하나의 클래스 Animal이 필요합니다. 세상에서 가능한 모든 동물을위한 수업.
나는 거친 규칙을 가지고있다.
기능 :모든 부분에서 다를 수 있습니다 : 인터페이스.
데이터 및 기능성, 부품은 대부분 동일하며 부품은 다릅니다.추상 클래스입니다.
약간의 변경으로 만 확장 된 경우 실제로 작동하는 데이터 및 기능 :보통 (콘크리트) 수업
데이터 및 기능성, 계획 변경 없음 :final 한정자를 가지는 보통 (concrete) 클래스.
데이터 및 기능성 : 읽기 전용 :enum 회원.
이것은 매우 거칠고 준비가되어 있으며 엄격하게 정의되지는 않았지만 모든 것이 읽기 전용 파일과 같이 고정되어있는 enum으로 변경하려는 인터페이스의 스펙트럼이 있습니다.
인터페이스는 작아야합니다. 정말 작아. 만약 당신이 정말로 당신의 객체를 분해한다면, 당신의 인터페이스는 아마도 매우 특정한 몇 가지 메소드와 속성을 포함 할 것입니다.
추상 클래스는 바로 가기입니다. PetBase의 모든 파생물이 공유 할 수있는 코드가 있습니까? 그렇다면 추상 수업을위한 시간입니다.
추상 클래스도 제한적입니다. 그들은 당신에게 자식 객체를 생성하는 훌륭한 지름길을 제공하지만, 주어진 객체는 하나의 추상 클래스 만 구현할 수 있습니다. 여러 번, 나는 이것이 추상적 인 클래스의 한계를 발견하고, 이것이 내가 많은 인터페이스를 사용하는 이유이다.
추상 클래스는 여러 인터페이스를 포함 할 수 있습니다. PetBase 추상 클래스는 IPet (소유자가 소유 한 애완 동물) 및 IDigestion (애완 동물이 먹거나 적어도해야 함)을 구현할 수 있습니다. 그러나 PetBase는 IMammal을 구현하지 않을 것입니다. 왜냐하면 모든 애완 동물이 포유 동물은 아니며 모든 포유 동물이 애완 동물이 아니기 때문입니다. PetBase를 확장하고 IMammal을 추가하는 MammalPetBase를 추가 할 수 있습니다. FishBase는 PetBase를 가지고 IFish를 추가 할 수 있습니다. IFish는 인터페이스로 ISwim 및 IUnderwaterBreather를 사용합니다.
네, 예제는 간단한 예제에서 광범위하게 복잡해졌지만 인터페이스와 추상 클래스가 함께 작동하는 방법에 대한 좋은 점 중 일부입니다.
인터페이스를 통한 기본 클래스에 대한 사례는 Submain .NET 코딩 가이드 라인에서 잘 설명되었습니다.
기본 클래스와 인터페이스인터페이스 유형은 부분적입니다. 값 설명, 잠재적 많은 객체 유형에서 지원됩니다. 용도 인터페이스 대신 기본 클래스 언제든지 가능할 때. 버전 관리에서 관점, 수업은보다 유연합니다 인터페이스보다. 수업을 통해 버전 1.0을 선적 한 후 버전 2.0 클래스에 새 메서드를 추가합니다. 방법이 추상적이 아닌 한, 기존의 모든 파생 클래스가 계속됩니다. 변경하지 않고 작동합니다.
인터페이스가 지원하지 않기 때문에 구현 상속, 클래스에 적용되는 패턴은 인터페이스에는 적용되지 않습니다. 추가 인터페이스에 대한 메소드는 동등합니다. 베이스에 추상 메소드 추가하기 수업; 클래스를 구현하는 모든 클래스 클래스가 새로운 방법을 구현하지 않습니다. 인터페이스는 다음과 같은 상황 :
- 관련없는 여러 클래스가 프로토콜을 지원하려고합니다.
- 이러한 클래스는 이미 기본 클래스를 예, 일부는 사용자 인터페이스 (UI) 컨트롤이며, 일부는 XML 웹 서비스).
- 집계는 적절하지 않거나 실용적이지 않습니다. 다른 모든 상황, 클래스 상속은 더 나은 모델입니다.
중요한 차이점 중 하나는 상속받을 수 있다는 것입니다.하나기본 클래스이지만 구현할 수 있습니다.많은인터페이스. 그렇다면 기본 클래스 만 사용하고 싶습니다.절대적으로 확실한다른 기본 클래스를 상속받을 필요가 없습니다. 또한 인터페이스가 커지면 독립적 인 기능을 정의하는 몇 가지 논리적 인 부분으로 나누어야합니다. 클래스가 모두 구현할 수있는 규칙이 없기 때문에 (또는 다른 인터페이스를 정의 할 수 없기 때문입니다 인터페이스를 그룹화하기 위해 모두 상속받는 인터페이스).
출처:http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C #은 지난 14 년 동안 성숙 해지고 발전한 훌륭한 언어입니다. 성숙한 언어는 우리가 사용할 수있는 많은 언어 기능을 우리에게 제공하기 때문에 이것은 우리 개발자에게 아주 좋습니다.
그러나 많은 힘이 많은 책임이됩니다. 이러한 기능 중 일부는 오용되거나 때로는 한 기능을 다른 기능보다 더 많이 사용하도록 선택한 이유를 이해하기 어렵습니다. 지난 수년간 많은 개발자들이 어려움을 겪어 왔던 기능 중 하나는 인터페이스를 사용하거나 추상 클래스를 사용하도록 선택하는 경우입니다. 두 가지 모두 장단점과 정확한 시간과 장소를 가지고 있습니다. 그러나 우리가 결정하는 방법 ???
둘 다 유형간에 공통 기능을 재사용합니다. 가장 명백한 차이점은 인터페이스는 기능에 대한 구현을 제공하지 않는다는 것입니다. 반면 추상 클래스를 사용하면 "기본"또는 "기본"동작을 구현하고 필요한 경우 클래스 파생 형식으로이 기본 동작을 "재정의"할 수 있습니다 .
이것은 모두 잘되고 좋은 것이며 코드를 재사용하고 소프트웨어 개발의 DRY (Do not 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"실사가 아닌 "가능"관계이며 다음을 설명하기 위해 인터페이스를 사용할 수 있습니다 :
// 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 메소드를 호출 할 수 있고 그 객체는 해당 유형에 따라 런타임에 어떤 동작을 취하는 지 알 수 있습니다.
다시 한번 말하지만 이것은 코드가 DRY 상태를 유지하도록 도와 주므로 동일한 핵심 기능 (ShowHowHumanSwims (human), ShowHowDuckSwims (duck) 등)을 수행하도록 객체를 호출하는 여러 메소드를 작성할 필요가 없습니다.
여기에서 인터페이스를 사용하면 호출하는 메소드가 어떤 유형인지 또는 어떻게 동작이 구현되는지에 대해 걱정할 필요가 없습니다. 인터페이스가 주어지면 각 객체는 Swim 메서드를 구현해야하므로 자체 코드에서이 메서드를 호출하고 Swim 메서드의 동작을 자체 클래스 내에서 처리 할 수 있도록해야합니다.
개요:
그래서 내 주요 어림짐작은 클래스 계층 구조에 대한 "기본"기능을 구현하거나 작업중인 클래스 또는 유형을 "a"관계 (예 : 푸들 ")로 공유하려는 경우 추상 클래스를 사용합니다. "유형의 개).
다른 한편으로는 "is a"관계는 없지만 무언가를하거나 "무언가를" "가지고있는"능력을 공유하는 유형이있는 경우 (예 : Duck은 인간이 아닙니다.) 그러나 오리와 인간의 몫 "능력"수영).
추상 클래스와 인터페이스 사이에주의해야 할 또 다른 차이점은 클래스가 하나의 인터페이스를 구현할 수 있지만 클래스는 하나의 추상 클래스 (또는 해당 클래스의 모든 클래스)에서만 상속받을 수 있다는 것입니다. 네, 클래스를 중첩 할 수 있고 상속 계층을 가질 수 있지만 많은 파생 된 클래스 정의를 가지고 상속 계층 구조를 가질 수는 없습니다.이 규칙은 C #에 적용됩니다. 다른 언어에서는 일반적으로이 작업을 수행 할 수 있습니다. 이러한 언어로 된 인터페이스가 부족하기 때문에).
인터페이스를 사용하여 인터페이스 분리 원칙 (Interface Separation Principle, ISP)을 준수 할 때도 기억하십시오. ISP는 클라이언트가 사용하지 않는 방법에 의존하도록 강요되어서는 안된다고 말합니다. 이러한 이유로 인터페이스는 특정 작업에만 집중해야하며 일반적으로 매우 작습니다 (예 : IDisposable, IComparable).
또 다른 팁은 작고 간결한 기능의 비트를 개발하고 인터페이스를 사용하는 경우입니다. 대규모 기능 단위를 디자인하는 경우 추상 클래스를 사용하십시오.
희망은 이것이 어떤 사람들을 위해 일들을 깨끗이 버리게 하옵소서!
또한 더 나은 예제를 생각하거나 뭔가를 지적하고 싶다면 아래의 주석에서 그렇게하십시오!
객체 지향 프로그래밍에 대해 처음 배우기 시작했을 때 상속을 사용하여 공통적 인 동작을 공유하는 쉽고 흔한 실수를 범했습니다. 심지어 그 동작이 객체의 본질에 필수적이지 않은 경우에도 마찬가지였습니다.
이 특정 질문에 많이 사용 된 예제를 추가로 작성하려면제비여자 친구, 자동차, 퍼지 담요 ... - 그래서 나는이 공통적 인 행동을 제공하는 Petable 클래스와 그것으로부터 상속받은 다양한 클래스들을 가질 수있었습니다.
그러나, 치유 가능하다는 것은 이러한 물체의 특성의 일부가 아닙니다. 그보다 훨씬 더 중요한 개념들이 있습니다.아르그들의 본질에 필수적입니다 - 여자 친구는 사람이고 차는 육로입니다 고양이는 포유 동물입니다 ...
행동은 먼저 인터페이스에 할당되어야하며 (클래스의 기본 인터페이스 포함) 기본 클래스로 승격되어야합니다. (a) 더 큰 클래스의 하위 집합 인 큰 그룹의 그룹에 공통적이어야합니다. "고양이"와 "사람"은 "포유류"의 하위 집합입니다.
잡기는 처음에했던 것보다 객체 지향 디자인을 충분히 잘 이해하고 나면 일반적으로 생각하지 않고도 자동으로이 작업을 수행하게됩니다. 따라서 "추상적 인 클래스가 아닌 인터페이스에 대한 코드"라는 진실한 진실은 너무 분명하여 누구나 자신이 말하는 것을 귀찮게 생각할 것입니다. 그리고 다른 의미를 읽는 것을 시도하기 시작합니다.
내가 추가 할 또 다른 것은 클래스가전혀추상 - 자식, 부모 또는 클라이언트에 노출 된 추상화되지 않은 상속되지 않은 멤버 또는 메서드가없는 경우 - 그렇다면 클래스는 무엇입니까? 어떤 경우에는 인터페이스로, 다른 경우에는 Null로 바꿀 수 있습니다.
일반적인 구현을 위해 추상 클래스를 사용하는 것에 대한 이전 의견은 확실히 주목할 만하다. 필자가 보지 못한 한 가지 이점은 인터페이스를 사용하면 단위 테스트 목적으로 모의 객체를 구현하는 것이 훨씬 쉬워진다는 것입니다. Jason Cohen이 설명한대로 IPet 및 PetBase를 정의하면 물리적 데이터베이스의 오버 헤드없이 다른 데이터 조건을 쉽게 조롱 할 수 있습니다 (실제 테스트 할 시간을 결정할 때까지).
기본 클래스가 의미하는 바를 모르고이 경우에 적용된다는 전제가 아니라면 기본 클래스를 사용하지 마십시오. 적용되면 사용하고, 그렇지 않으면 인터페이스를 사용하십시오. 그러나 작은 인터페이스에 대한 대답을 메모하십시오.
공개 상속은 OOD에서 남용되고 대부분의 개발자가 깨닫거나 살아 가려고하는 것보다 훨씬 더 많이 표현합니다. 자세한 내용은리 스키프 대체 원리
즉, A가 "B"이면 A는 노출하는 모든 방법에 대해 B 이상을 필요로하고 B보다 적게 배급합니다.
개념적으로, 인터페이스는 객체가 제공 할 메소드 세트를 공식적으로, 그리고 정식으로 정의하는데 사용됩니다. 형식적으로는 메소드 이름과 서명 세트를 의미하며, 반 공식적으로는 이러한 메소드와 관련된 사람이 읽을 수있는 문서를 의미합니다. 인터페이스는 API에 대한 설명 일뿐입니다 (결국 API는 Application Programmer인터페이스), 어떤 구현도 포함 할 수 없으며 Interface를 사용하거나 실행할 수 없습니다. 그들은 단지 당신이 어떻게 객체와 상호 작용해야하는지에 대한 명시적인 계약을합니다.
클래스는 구현을 제공하며 0 개, 하나 이상의 인터페이스를 구현한다고 선언 할 수 있습니다. 클래스가 상속되는 경우, 규칙은 클래스 이름 앞에 "Base"를 붙이는 것입니다.
기본 클래스와 추상 기본 클래스 (ABC)는 구별됩니다. ABC는 인터페이스와 구현을 함께 사용합니다. 컴퓨터 프로그래밍 외부의 추상화는 "요약"즉 "추상 == 인터페이스"를 의미합니다. 그런 다음 추상 기본 클래스는 상속 될 빈, 부분 또는 전체 구현뿐만 아니라 인터페이스를 모두 설명 할 수 있습니다.
인터페이스를 사용하는시기와 추상적 인 기본 클래스 대 클래스에 대한 의견은 현재 개발중인 것과 개발중인 언어에 따라 크게 달라질 것입니다. 인터페이스는 Java 또는 C #과 같은 정적 유형 언어와 만 연관되어 있지만 동적 유형의 언어는 인터페이스 및 추상 기본 클래스도 가질 수 있습니다. 예를 들어 파이썬에서 클래스는 다음과 같이 구분됩니다.도구인터페이스 및 클래스의 인스턴스 인 객체가 포함되어 있으며~을 제공하다그 인터페이스. 동적 언어에서는 동일한 클래스의 두 인스턴스 인 두 객체가 완전히 제공한다고 선언 할 수 있습니다다른인터페이스. 파이썬에서는 메소드가 클래스의 모든 객체 사이에서 공유되는 동안 이것은 객체 속성에만 가능합니다. 그러나 Ruby에서 객체는 인스턴스별로 메서드를 가질 수 있으므로 동일한 클래스의 두 객체 사이의 인터페이스는 프로그래머가 원하는만큼 다양 할 수 있습니다 (그러나 Ruby에는 명시적인 인터페이스 선언 방법이 없습니다).
동적 인 언어에서 객체에 대한 인터페이스는 종종 객체를 들여다보고 객체가 제공하는 어떤 메소드 (Leap Before You Leap)를 묻거나 객체에 대해 원하는 인터페이스를 사용하려고 시도하고 객체가 그 인터페이스를 제공하지 않습니다 (허가보다 용서를 구하는 것이 더 쉽습니다). 이것은 두 개의 인터페이스가 동일한 메소드 이름을 갖고 있지만 의미 상으로 다른 "오 탐지 (false positives)"로 이어질 수 있지만, 모든 가능한 용도를 예상하기 위해 선행을 지정할 필요가 없으므로 코드가보다 유연합니다. 귀하의 코드입니다.
명심해야 할 또 다른 옵션은 "has-a"관계를 사용하는 것입니다 (별칭은 "용어"또는 "구성"으로 구현 됨). 때때로 이것은 "is-a"상속을 사용하는 것보다 구조화하는 데 더 깔끔하고 유연한 방법입니다.
개와 고양이 모두 애완 동물을 "가지고있다"고 말하는 것은 논리적으로는별로 의미가 없을 지 모르지만 일반적인 다중 상속 함정은 피할 수 있습니다.
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의 private 멤버에 액세스 할 수 없기 때문에). Dog와 Cat은 다른 곳에서 상속받을 수있는 공간을 남겨 둡니다. - 분명히 포유류 계급.
비공개 액세스가 필요하지 않으며 일반 애완 동물 참조 / 포인터를 사용하여 개와 고양이를 참조 할 필요가없는 경우 작문이 바람직합니다. 인터페이스는 일반적인 참조 기능을 제공하고 코드의 자세한 정보를 줄이는 데 도움을 줄 수 있지만 잘못 구성된 경우에는 혼란 스러울 수 있습니다. 상속은 개인 회원이 액세스해야 할 때 유용하며, 사용시 애완 동물 클래스에 개와 고양이 클래스를 연결하는 데 많은 돈을 들이고 있습니다. 이는 급히 지불해야하는 비용입니다.
상속, 구성 및 인터페이스 사이에는 항상 올바른 방법이 하나도 없으며 세 가지 옵션을 모두 조화롭게 사용하는 방법을 고려하는 것이 좋습니다. 세 가지 중에서 상속은 일반적으로 가장 자주 사용되지 않아야하는 옵션입니다.
추상 클래스를 통한 인터페이스 선호
이론적 해석, 고려해야 할 주요 사항은 이미 여기에 언급되어 있습니다.
[1] 더 많은 코드를 추가합니다. 그러나 간결함이 가장 큰 관심사라면 Java를 처음부터 피해야합니다.
[2] Joshua Bloch, Effective Java, 항목 16-18.
요구 사항에 따라 다릅니다. IPet가 충분히 간단하다면, 나는 그것을 구현하는 것을 선호 할 것이다. 그렇지 않으면 PetBase가 복제하지 않으려는 많은 기능을 구현 한 다음에는 가지고 있어야합니다.
기본 클래스를 구현할 때의 단점은override
(또는new
) 기존 방법. 이것은 가상 메소드가되므로 객체 인스턴스를 사용하는 방법에주의해야한다는 것을 의미합니다.
마지막으로, .NET의 단일 상속이 나를 죽입니다. 간단한 예 : 사용자가 컨트롤을 만들고 있다고 가정 해 보겠습니다.UserControl
. 하지만 이제는 상속받을 수 없게됩니다.PetBase
. 이것은 당신을 재구성하는 것을 강요합니다.PetBase
대신에 일원이다.
@ 조엘 : 일부 언어 (예 : C ++)에서는 다중 상속을 허용합니다.
나는 필요할 때까지 보통 구현하지 않습니다. 나는 좀 더 많은 유연성을 제공하기 때문에 추상 클래스보다 인터페이스를 선호한다. 상속하는 클래스 중 일부에서 공통적 인 동작이있는 경우이를 옮겨서 추상 기본 클래스를 만듭니다. 나는 본질적으로 같은 목적의 서버이기 때문에 두 가지 모두에 대한 필요성을 알지 못한다. 두 가지 모두가 해결책이 과도하게 설계된 나쁜 코드 냄새 (imho)이다.
C #과 관련하여, 어떤면에서는 인터페이스와 추상 클래스가 서로 바뀔 수 있습니다. 그러나 차이점은 다음과 같습니다. i) 인터페이스가 코드를 구현할 수 없습니다. ii) 이것 때문에 인터페이스는 스택을 하위 클래스로 호출 할 수 없습니다. 그리고 ⅲ) 추상 클래스는 클래스 상에 상속 될 수있는 반면, 다중 인터페이스는 클래스 상에 구현 될 수있다.
def로 인터페이스는 다른 코드와 통신하기위한 레이어를 제공합니다. 클래스의 모든 public 속성 및 메서드는 기본적으로 암시 적 인터페이스를 구현합니다. 인터페이스를 역할로 정의 할 수도 있습니다. 어떤 클래스도 그 역할을 수행해야 할 때 인터페이스를 구현하는 클래스에 따라 다른 구현 형태를 제공해야합니다. 따라서 인터페이스에 대해 이야기 할 때, 당신은 다형성에 대해 이야기하고 있으며, 기본 클래스에 관해 말할 때, 당신은 상속에 대해 이야기하고 있습니다. oops의 두 가지 개념 !!!
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"관계를 가져야합니다. 인터페이스는 "구현"관계를 나타냅니다. 상속자가 관계를 유지할 때만 기본 클래스 만 사용하십시오.