Źródło : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C # to wspaniały język, który dojrzał i ewoluował w ciągu ostatnich 14 lat. Jest to świetne dla nas, programistów, ponieważ dojrzały język zapewnia nam mnóstwo funkcji językowych, które są do naszej dyspozycji.
Jednak z dużą mocą staje się duża odpowiedzialność. Niektóre z tych funkcji mogą być niewłaściwie używane, a czasem trudno jest zrozumieć, dlaczego zdecydujesz się korzystać z jednej funkcji nad drugą. Przez lata funkcja, z którą widziałem wielu programistów, polega na tym, kiedy wybrać interfejs lub klasę abstrakcyjną. Oba mają wady i zalety, a każdy z nich ma odpowiedni czas i miejsce. Ale jak zdecydować?
Oba zapewniają ponowne wykorzystanie wspólnej funkcjonalności między typami. Najbardziej oczywistą różnicą jest to, że interfejsy nie zapewniają żadnej implementacji dla ich funkcjonalności, podczas gdy klasy abstrakcyjne pozwalają na implementację niektórych „podstawowych” lub „domyślnych” zachowań, a następnie mają możliwość „zastąpienia” tego domyślnego zachowania typami pochodnymi klas, jeśli to konieczne .
Wszystko to dobrze i dobrze, zapewnia świetne ponowne użycie kodu i przestrzega zasady DRY (Don't Repeat Yourself) tworzenia oprogramowania. Klasy abstrakcyjne są świetne do użycia, gdy masz relację „jest”.
Na przykład: Golden retriever „jest” typem psa. Pudel też. Oboje mogą szczekać, tak jak wszystkie psy. Możesz jednak stwierdzić, że park pudli różni się znacznie od „domyślnej” kory psa. Dlatego warto wdrożyć coś w następujący sposób:
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
//
Jak widać, byłby to świetny sposób na zachowanie stanu DRY w kodzie i umożliwienia wywoływania implementacji klasy podstawowej, gdy dowolny z typów może polegać tylko na domyślnej Bark zamiast implementacji specjalnego przypadku. Klasy takie jak GoldenRetriever, Boxer, Lab mogą wszyscy odziedziczyć „domyślną” (klasę basu) Kora bez żadnych opłat tylko dlatego, że implementują klasę abstrakcyjną Dog.
Ale jestem pewien, że już to wiedziałeś.
Jesteś tutaj, ponieważ chcesz zrozumieć, dlaczego możesz chcieć wybrać interfejs zamiast klasy abstrakcyjnej lub odwrotnie. Cóż, jednym z powodów, dla których możesz chcieć wybrać interfejs zamiast klasy abstrakcyjnej jest to, że nie masz lub chcesz zapobiec domyślnej implementacji. Wynika to zwykle z tego, że typy, które implementują interfejs niezwiązany w relacji „jest”. W rzeczywistości nie muszą być w ogóle powiązane, z wyjątkiem faktu, że każdy typ „jest w stanie” lub ma „zdolność” do zrobienia czegoś lub posiadania czegoś.
Co to do cholery znaczy? Na przykład: człowiek nie jest kaczką… a kaczka nie jest człowiekiem. Dość oczywiste. Jednak zarówno kaczka, jak i człowiek mają „zdolność” pływania (biorąc pod uwagę, że człowiek zdał lekcje pływania w 1. klasie :)). Ponadto, ponieważ kaczka nie jest człowiekiem lub odwrotnie, nie jest to realizacja „jest”, ale relacja „jest w stanie”, a my możemy użyć interfejsu do zilustrowania tego:
// 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!
Korzystanie z interfejsów takich jak powyższy kod pozwoli ci przekazać obiekt do metody, która „jest w stanie” coś zrobić. Kod nie dba o to, jak to robi… Wie tylko, że może wywoływać metodę Swim na tym obiekcie, a ten obiekt będzie wiedział, jakie zachowanie podejmie w czasie wykonywania na podstawie jego typu.
Po raz kolejny pomaga to Twojemu kodowi pozostać SUCHYM, dzięki czemu nie będziesz musiał pisać wielu metod, które wywołują obiekt, aby wykonać tę samą funkcję podstawową (ShowHowHumanSwims (human), ShowHowDuckSwims (duck) itp.)
Korzystanie z interfejsu w tym miejscu pozwala metodom wywoływania nie martwić się o to, jaki typ jest który lub w jaki sposób zachowanie jest realizowane. Po prostu wie, że biorąc pod uwagę interfejs, każdy obiekt będzie musiał zaimplementować metodę Swim, więc można bezpiecznie wywołać go we własnym kodzie i pozwolić, aby zachowanie metody Swim było obsługiwane w ramach jego własnej klasy.
Podsumowanie:
Więc moją główną zasadą jest użycie klasy abstrakcyjnej, gdy chcesz zaimplementować funkcję „domyślną” dla hierarchii klas lub / i klasy lub typy, z którymi pracujesz, mają relację „jest” (np. Pudel „jest ”Typ psa).
Z drugiej strony używaj interfejsu, gdy nie masz relacji „jest”, ale masz typy, które dzielą „zdolność” do zrobienia czegoś lub posiadania czegoś (np. Kaczka „nie jest” człowiekiem. Jednak kaczka i człowiek dzielą się „Umiejętność” pływania).
Inną różnicą, na którą należy zwrócić uwagę między klasami abstrakcyjnymi a interfejsami, jest to, że klasa może implementować jeden lub wiele interfejsów, ale klasa może dziedziczyć tylko z JEDNEJ klasy abstrakcyjnej (lub dowolnej innej klasy pod tym względem). Tak, możesz zagnieżdżać klasy i mieć hierarchię dziedziczenia (którą wiele programów robi i powinno mieć), ale nie możesz dziedziczyć dwóch klas w jednej definicji klasy pochodnej (ta reguła dotyczy C #. W niektórych innych językach możesz to zrobić, zwykle tylko z powodu braku interfejsów w tych językach).
Pamiętaj także o korzystaniu z interfejsów w celu przestrzegania zasady segregacji interfejsów (ISP). ISP stwierdza, że żaden klient nie powinien być zmuszany do polegania na metodach, których nie używa. Z tego powodu interfejsy powinny koncentrować się na konkretnych zadaniach i zwykle są bardzo małe (np. IDisposable, IComparable).
Inną wskazówką jest, jeśli opracowujesz małe, zwięzłe elementy funkcjonalności, korzystaj z interfejsów. Jeśli projektujesz duże jednostki funkcjonalne, użyj klasy abstrakcyjnej.
Mam nadzieję, że to wyjaśni niektóre osoby!
Również jeśli możesz wymyślić jakieś lepsze przykłady lub chcesz coś wskazać, zrób to w komentarzach poniżej!