출처 : 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 abstract 클래스를 구현하기 때문에 "기본"(베이스 클래스) Bark를 무료로 상속 할 수 있습니다.
그러나 나는 당신이 이미 그것을 알고 있다고 확신합니다.
추상 클래스를 통해 인터페이스를 선택하려는 이유를 이해하고 싶거나 그 반대의 이유도 여기에 있습니다. 추상 클래스를 통해 인터페이스를 선택해야하는 한 가지 이유는 기본 구현이 없거나 방지하려는 경우입니다. 일반적으로 인터페이스를 구현하는 유형은 "is a"관계와 관련이 없기 때문입니다. 실제로, 그들은 각 유형이 무언가를 할 수 있거나 무언가를 가질 수있는 능력이 있다는 것을 제외하고는 전혀 관련 될 필요가 없습니다.
도대체 그게 무슨 뜻입니까? 예를 들어, 인간은 오리가 아니며 오리는 인간이 아닙니다. 꽤 분명합니다. 그러나 오리와 인간 모두 수영 할 수있는 능력이 있습니다 (1 학년에 인간이 수영 수업을 통과 한 경우). 또한 오리는 인간이 아니거나 그 반대이기 때문에 이것은“현상”이 아니라“유능한”관계이며 우리는이를 설명하기 위해 인터페이스를 사용할 수 있습니다.
// 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 메소드의 동작을 자체 클래스 내에서 처리 할 수 있습니다.
요약:
따라서 제 기본 규칙은 클래스 계층 구조 및 / 또는 작업중인 클래스 또는 유형에 "기본"기능을 구현하려는 경우 추상 클래스를 사용하는 것입니다 (예 : 푸들 " ”유형의 개).
반면에“is a”관계는 없지만 무언가를 할 수있는 능력을 공유하는 유형 (예 : Duck은 인간이 아님) 인 경우 인터페이스를 사용하십시오. "수영 능력").
추상 클래스와 인터페이스의 또 다른 차이점은 클래스는 일대 다 인터페이스를 구현할 수 있지만 클래스는 하나의 추상 클래스 (또는 해당 문제에 대한 모든 클래스)에서만 상속 할 수 있다는 것입니다. 예, 클래스를 중첩하고 상속 계층 구조를 가질 수 있지만 (많은 프로그램이 갖고 있어야 함) 하나의 파생 클래스 정의에서 두 클래스를 상속 할 수 없습니다 (이 규칙은 C #에 적용됩니다. 다른 언어에서는 일반적으로이를 수행 할 수 있음) 이 언어의 인터페이스가 부족하기 때문에).
또한 인터페이스를 사용하여 인터페이스 분리 원칙 (ISP)을 준수 할 때도 기억하십시오. ISP는 클라이언트가 사용하지 않는 방법에 의존해서는 안된다고 명시하고 있습니다. 이러한 이유로 인터페이스는 특정 작업에 중점을 두어야하며 일반적으로 매우 작습니다 (예 : IDisposable, IComparable).
또 다른 팁은 작고 간결한 기능을 개발하는 경우 인터페이스를 사용하는 것입니다. 큰 기능 단위를 디자인하는 경우 추상 클래스를 사용하십시오.
이것이 어떤 사람들에게 도움이 되길 바랍니다!
또한 더 좋은 예를 생각하거나 무언가를 지적하고 싶다면 아래 의견에서 그렇게하십시오!