Fonte : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C # è un linguaggio meraviglioso che è maturato ed evoluto negli ultimi 14 anni. Questo è ottimo per noi sviluppatori perché un linguaggio maturo ci fornisce una pletora di funzionalità linguistiche che sono a nostra disposizione.
Tuttavia, con molto potere diventa molta responsabilità. Alcune di queste funzionalità possono essere utilizzate in modo improprio o, a volte, è difficile capire perché si scelga di utilizzare una funzionalità rispetto a un'altra. Nel corso degli anni, una caratteristica con cui ho visto molti sviluppatori lottare è quando scegliere di utilizzare un'interfaccia o scegliere di utilizzare una classe astratta. Entrambi hanno vantaggi e svantaggi e il tempo e il luogo corretti per utilizzarli ciascuno. Ma come decidiamo ???
Entrambi prevedono il riutilizzo di funzionalità comuni tra i tipi. La differenza più evidente subito è che le interfacce non forniscono alcuna implementazione per la loro funzionalità mentre le classi astratte ti consentono di implementare un comportamento "base" o "predefinito" e quindi hanno la possibilità di "sovrascrivere" questo comportamento predefinito con i tipi derivati dalle classi, se necessario .
Tutto questo va bene e prevede un grande riutilizzo del codice e aderisce al principio DRY (Don't Repeat Yourself) dello sviluppo del software. Le classi astratte sono fantastiche da usare quando si ha una relazione "è un".
Ad esempio: un golden retriever "è un" tipo di cane. Quindi è un barboncino. Entrambi possono abbaiare, come tutti i cani. Tuttavia, potresti voler affermare che il barboncino è significativamente diverso dalla corteccia di cane "predefinita". Pertanto, potrebbe avere senso implementare qualcosa come segue:
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
//
Come puoi vedere, questo sarebbe un ottimo modo per mantenere il tuo codice DRY e consentire l'implementazione della classe di base quando uno qualsiasi dei tipi può semplicemente fare affidamento sul Bark predefinito invece di un'implementazione del caso speciale. Classi come GoldenRetriever, Boxer, Lab potrebbero ereditare gratuitamente la corteccia "default" (classe di basso) solo perché implementano la classe astratta Dog.
Ma sono sicuro che lo sapevi già.
Sei qui perché vuoi capire perché potresti voler scegliere un'interfaccia rispetto a una classe astratta o viceversa. Bene, uno dei motivi per cui potresti voler scegliere un'interfaccia rispetto a una classe astratta è quando non hai o vuoi impedire un'implementazione predefinita. Questo di solito è dovuto al fatto che i tipi che implementano l'interfaccia non sono correlati in una relazione "is a". In realtà, non devono essere affatto collegati, tranne per il fatto che ogni tipo "è in grado" o ha "l'abilità" di fare qualcosa o avere qualcosa.
Cosa diavolo significa? Bene, per esempio: un essere umano non è un'anatra ... e un'anatra non è un essere umano. Abbastanza ovvio. Tuttavia, sia un'anatra che un essere umano hanno "la capacità" di nuotare (dato che l'essere umano ha passato le sue lezioni di nuoto in prima elementare :)). Inoltre, poiché un'anatra non è un essere umano o viceversa, questa non è una "è una" realizzazione, ma una relazione "è capace" e possiamo usare un'interfaccia per illustrare che:
// 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!
L'uso di interfacce come il codice sopra ti permetterà di passare un oggetto in un metodo che "è in grado" di fare qualcosa. Al codice non importa come lo fa ... Tutto quello che sa è che può chiamare il metodo Swim su quell'oggetto e quell'oggetto saprà quale comportamento assumere in fase di esecuzione in base al suo tipo.
Ancora una volta, questo aiuta il tuo codice a rimanere ASCIUTTO in modo da non dover scrivere più metodi che chiamano l'oggetto per preformare la stessa funzione principale (ShowHowHumanSwims (umano), ShowHowDuckSwims (anatra), ecc.)
L'uso di un'interfaccia qui consente ai metodi di chiamata di non doversi preoccupare di quale tipo sia quale o come viene implementato il comportamento. Sa solo che, data l'interfaccia, ogni oggetto dovrà aver implementato il metodo Swim, quindi è sicuro chiamarlo nel proprio codice e consentire che il comportamento del metodo Swim sia gestito all'interno della propria classe.
Sommario:
Quindi la mia regola empirica principale è usare una classe astratta quando si desidera implementare una funzionalità "predefinita" per una gerarchia di classi o / e le classi o i tipi con cui si sta lavorando condividono una relazione "is a" (es. Barboncino "è un "Tipo di cane).
D'altra parte usa un'interfaccia quando non hai una relazione “è una” ma hai tipi che condividono “la capacità” di fare qualcosa o avere qualcosa (es. Anatra “non è” un essere umano. Tuttavia, anatra e condivisione umana "La capacità" di nuotare).
Un'altra differenza da notare tra le classi astratte e le interfacce è che una classe può implementare da una a molte interfacce ma una classe può ereditare solo da UNA classe astratta (o qualsiasi classe per quella materia). Sì, puoi nidificare le classi e avere una gerarchia di ereditarietà (che molti programmi hanno e dovrebbero avere) ma non puoi ereditare due classi in una definizione di classe derivata (questa regola si applica a C #. In alcune altre lingue puoi farlo, di solito solo a causa della mancanza di interfacce in queste lingue).
Ricorda anche quando usi le interfacce per aderire al principio di segregazione dell'interfaccia (ISP). L'ISP afferma che nessun client dovrebbe essere costretto a dipendere da metodi che non utilizza. Per questo motivo le interfacce dovrebbero essere focalizzate su compiti specifici e di solito sono molto piccole (es. IDisposable, IComparable).
Un altro suggerimento è se stai sviluppando piccole funzionalità concise, usa le interfacce. Se si progettano unità funzionali di grandi dimensioni, utilizzare una classe astratta.
Spero che questo chiarisca le cose per alcune persone!
Inoltre, se riesci a pensare a qualche esempio migliore o vuoi segnalare qualcosa, per favore fallo nei commenti qui sotto!