Quando utilizzare un’interfaccia invece di una class astratta e viceversa?

Questa potrebbe essere una domanda OOP generica. Volevo fare un confronto generico tra un’interfaccia e una class astratta sulla base del loro utilizzo.

Quando si vorrebbe usare un’interfaccia e quando si vorrebbe usare una class astratta ?

Ho scritto un articolo a riguardo:

Classi e interfacce astratte

riassumendo:

Quando parliamo di classi astratte definiamo le caratteristiche di un tipo di object; specificando cos’è un object .

Quando parliamo di un’interfaccia e definiamo le funzionalità che promettiamo di fornire, stiamo parlando di stabilire un contratto su ciò che l’object può fare.

Una class astratta può avere stato o funzionalità condivisi. Un’interfaccia è solo una promise per fornire lo stato o la funzionalità. Una buona class astratta ridurrà la quantità di codice che deve essere riscritta perché è ansible condividere la funzionalità o lo stato. L’interfaccia non ha informazioni definite da condividere

Personalmente, non ho quasi mai bisogno di scrivere classi astratte.

La maggior parte delle volte vedo l’utilizzo di classi astratte (errate), perché l’autore della class astratta utilizza il modello “Metodo modello”.

Il problema con il “metodo Template” è che è quasi sempre un po ‘rientranti – la class “derivata” conosce non solo il metodo “astratto” della sua class base che sta implementando, ma anche i metodi pubblici della class base , anche se la maggior parte delle volte non ha bisogno di chiamarli.

(Eccessivamente semplificato) esempio:

 abstract class QuickSorter { public void Sort(object[] items) { // implementation code that somewhere along the way calls: bool less = compare(x,y); // ... more implementation code } abstract bool compare(object lhs, object rhs); } 

Quindi, qui l’autore di questa class ha scritto un algoritmo generico e intende utilizzarlo “specializzandolo” fornendo i propri “ganci” – in questo caso, un metodo di “confronto”.

Quindi l’uso previsto è qualcosa del genere:

 class NameSorter : QuickSorter { public bool compare(object lhs, object rhs) { // etc. } } 

Il problema con questo è che hai indebitamente accoppiato insieme due concetti:

  1. Un modo per confrontare due elementi (quale elemento dovrebbe andare per primo)
  2. Un metodo di ordinamento degli oggetti (ad esempio quicksort vs merge sort ecc.)

Nel codice di cui sopra, in teoria, l’autore del metodo “compare” può riutilizzare di nuovo il metodo di “superclass” della superclass … anche se in pratica non vorranno o non dovranno farlo.

Il prezzo che si paga per questo accoppiamento non necessario è che è difficile cambiare la superclass, e nella maggior parte dei linguaggi OO, imansible modificarlo in fase di runtime.

Il metodo alternativo consiste nell’utilizzare il modello di progettazione “Strategia” invece:

 interface IComparator { bool compare(object lhs, object rhs); } class QuickSorter { private readonly IComparator comparator; public QuickSorter(IComparator comparator) { this.comparator = comparator; } public void Sort(object[] items) { // usual code but call comparator.Compare(); } } class NameComparator : IComparator { bool compare(object lhs, object rhs) { // same code as before; } } 

Notate ora: abbiamo solo interfacce e implementazioni concrete di queste interfacce. In pratica, non hai davvero bisogno di nient’altro per realizzare un design OO di alto livello.

Per “hide” il fatto che abbiamo implementato “l’ordinamento dei nomi” usando una class “QuickSort” e un “NameComparator”, potremmo ancora scrivere un metodo di fabbrica da qualche parte:

 ISorter CreateNameSorter() { return new QuickSorter(new NameComparator()); } 

Ogni volta che hai una lezione astratta puoi farlo … anche quando c’è una relazione di rientro naturale tra la class base e quella derivata, di solito si paga per renderli espliciti.

Un ultimo pensiero: tutto ciò che abbiamo fatto sopra è “comporre” una funzione “NameSorting” usando una funzione “QuickSort” e una funzione “NameComparison” … in un linguaggio di programmazione funzionale, questo stile di programmazione diventa ancora più naturale, con meno codice

OK, avendo appena “fatto da battistrada” questo me stesso – qui è in parole povere (sentiti libero di correggermi se ho torto) – So che questo argomento è oooooold, ma qualcun altro potrebbe inciampare in esso un giorno …

Le classi astratte ti consentono di creare un progetto e ti consentono inoltre di COSTRUIRE (implementare) le proprietà e i metodi che vuoi che TUTTI i suoi discendenti posseggano.

Un’interfaccia, d’altra parte, consente solo di dichiarare che si desidera che le proprietà e / oi metodi con un nome dato esistano in tutte le classi che la implementano, ma non specifica come implementarla. Inoltre, una class può implementare molte interfacce, ma può estendere solo una class astratta. Un’interfaccia è più di uno strumento architettonico di alto livello (che diventa più chiaro se si inizia a comprendere i modelli di progettazione): un Abstract ha un piede in entrambi i campi e può svolgere anche parte del lavoro sporco.

Perché usarne uno sull’altro? Il primo consente una definizione più concreta dei discendenti – il secondo consente un maggiore polimorfismo . Quest’ultimo punto è importante per l’utente finale / programmatore, che può utilizzare queste informazioni per implementare l’AP I (interfaccia) in una varietà di combinazioni / forms in base alle proprie esigenze.

Penso che questo sia stato il momento “lightbulb” per me – pensate alle interfacce meno dalla prospettiva dell’autore e più da quella di qualsiasi coder che arriva più tardi nella catena che sta aggiungendo l’implementazione a un progetto, o estendendo un’API.

I miei due centesimi:

Un’interfaccia definisce fondamentalmente un contratto, che ogni class di implementazione deve rispettare (implementare i membri dell’interfaccia). Non contiene alcun codice.

D’altra parte, una class astratta può contenere codice e potrebbero esserci alcuni metodi contrassegnati come astratti che una class ereditaria deve implementare.

Le rare situazioni in cui ho usato le classi astratte è quando ho alcune funzionalità predefinite che la class ereditaria potrebbe non essere interessante nel sovrascrivere, per esempio una class base astratta, da cui ereditano alcune classi specializzate.

Esempio (molto rudimentale!): Si consideri una class base chiamata Cliente che ha metodi astratti come CalculatePayment() , CalculateRewardPoints() e alcuni metodi non astratti come GetName() , SavePaymentDetails() .

Classi specializzate come RegularCustomer e GoldCustomer erediteranno dalla class base del Customer e implementeranno la propria logica del metodo CalculatePayment() e CalculateRewardPoints() , ma riutilizzeranno i GetName() e SavePaymentDetails() .

È ansible aggiungere più funzionalità a una class astratta (metodi non astratti) senza influire sulle classi child che utilizzavano una versione precedente. Mentre l’aggiunta di metodi all’interfaccia influirebbe su tutte le classi che la implementano, poiché ora dovranno implementare i membri dell’interfaccia appena aggiunti.

Una class astratta con tutti i membri astratti sarebbe simile a un’interfaccia.

Quando fare ciò che è una cosa molto semplice se hai il concetto chiaro nella tua mente.

Le classi astratte possono essere derivate mentre le interfacce possono essere implementate. C’è una certa differenza tra i due. Quando si ricava una class Abstract, la relazione tra la class derivata e la class base è ‘è una’ relazione. ad esempio, un cane è un animale, una pecora è un animale, il che significa che una class derivata sta ereditando alcune proprietà dalla class base.

Mentre per l’implementazione di interfacce, la relazione è “può essere”. ad esempio, un cane può essere un cane spia. Un cane può essere un cane da circo. Un cane può essere un cane da corsa. Ciò significa che implementi determinati metodi per acquisire qualcosa.

Spero di essere chiaro.

Se stai guardando java come linguaggio OOP,

“l’ interfaccia non fornisce l’implementazione del metodo ” non è più valida con il lancio di Java 8. Ora java fornisce l’implementazione nell’interfaccia per i metodi predefiniti.

In termini semplici, mi piacerebbe usare

interfaccia: per implementare un contratto con più oggetti non correlati. Fornisce capacità ” HA UN “.

class astratta: per implementare lo stesso comportamento o un comportamento diverso tra più oggetti correlati. Stabilisce una relazione ” IS A “.

Il sito Web Oracle offre differenze chiave tra l’ interface e la class abstract .

Prendi in considerazione l’utilizzo di classi astratte se:

  1. Vuoi condividere il codice tra diverse classi strettamente correlate.
  2. Ti aspetti che le classi che estendono la tua class astratta abbiano molti metodi o campi comuni o richiedano modificatori di accesso diversi da quelli pubblici (come protetti e privati).
  3. Si desidera dichiarare campi non statici o non finali.

Prendi in considerazione l’utilizzo di interfacce se:

  1. Ti aspetti che le classi non correlate implementino la tua interfaccia. Ad esempio, molti oggetti non collegati possono implementare un’interfaccia Serializable .
  2. Si desidera specificare il comportamento di un particolare tipo di dati, ma non si preoccupa di chi implementa il suo comportamento.
  3. Vuoi sfruttare l’ereditarietà multipla del tipo.

Esempio:

Classe astratta (relazione IS A )

Reader è una class astratta.

BufferedReader è un Reader

FileReader è un Reader

FileReader e BufferedReader sono utilizzati per scopi comuni: lettura dei dati e relativi alla class Reader .

Interfaccia (ha una capacità)

Serializable è un’interfaccia.

Supponiamo che tu abbia due classi nella tua applicazione, che stanno implementando un’interfaccia Serializable

Employee implements Serializable

Game implements Serializable

Qui non è ansible stabilire alcuna relazione attraverso un’interfaccia Serializable tra Employee e Game , che sono destinati a scopi diversi. Entrambi sono in grado di serializzare lo stato e la comparasion finisce lì.

Dai un’occhiata a questi post:

Come dovrei aver spiegato la differenza tra un’interfaccia e una class astratta?

Ho scritto un articolo su quando usare una class astratta e quando usare un’interfaccia. C’è molta più differenza tra loro che “un IS-A … e un CAN-DO …”. Per me, quelle sono risposte in scatola. Ho citato alcuni motivi per utilizzare uno di essi. Spero che sia d’aiuto.

http://codeofdoom.com/wordpress/2009/02/12/learn-this-when-to-use-an-abstract-class-and-an-interface/

1.Se si sta creando qualcosa che fornisce funzionalità comuni a classi non correlate, utilizzare un’interfaccia.

2.Se si sta creando qualcosa per oggetti strettamente correlati in una gerarchia, utilizzare una class astratta.

Le classi possono ereditare da una sola class base, quindi se si desidera utilizzare classi astratte per fornire il polimorfismo a un gruppo di classi, tutte devono ereditare da quella class. Le classi astratte possono anche fornire membri che sono già stati implementati. Pertanto, è ansible garantire una certa quantità di funzionalità identiche con una class astratta, ma non con un’interfaccia.

Ecco alcuni consigli per aiutarti a decidere se utilizzare un’interfaccia o una class astratta per fornire il polimorfismo per i tuoi componenti.

  • Se prevedi di creare più versioni del tuo componente, crea una class astratta. Le classi astratte forniscono un modo semplice e facile per la versione dei componenti. Aggiornando la class base, tutte le classi ereditanti vengono automaticamente aggiornate con la modifica. Le interfacce, d’altra parte, non possono essere modificate una volta create in quel modo. Se è necessaria una nuova versione di un’interfaccia, è necessario creare un’intera nuova interfaccia.
  • Se la funzionalità che stai creando sarà utile su una vasta gamma di oggetti disparati, usa un’interfaccia. Le classi astratte dovrebbero essere utilizzate principalmente per oggetti strettamente correlati, mentre le interfacce sono più adatte per fornire funzionalità comuni a classi non correlate.
  • Se stai progettando bit di funzionalità piccoli e concisi, utilizza le interfacce. Se stai progettando unità funzionali di grandi dimensioni, usa una class astratta.
  • Se si desidera fornire funzionalità comuni e implementate tra tutte le implementazioni del componente, utilizzare una class astratta. Le classi astratte consentono di implementare parzialmente la class, mentre le interfacce non contengono alcuna implementazione per i membri.

Copiato da:
http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx

Prendi in considerazione l’utilizzo di classi astratte se una di queste affermazioni si applica alla tua situazione:

  1. Vuoi condividere il codice tra diverse classi strettamente correlate.
  2. Ti aspetti che le classi che estendono la tua class astratta abbiano molti metodi o campi comuni o richiedano modificatori di accesso diversi da quelli pubblici (come protetti e privati).
  3. Si desidera dichiarare campi non statici o non finali. Ciò consente di definire metodi che possono accedere e modificare lo stato dell’object a cui appartengono.

Prendi in considerazione l’utilizzo di interfacce se una di queste affermazioni si applica alla tua situazione:

  1. Ti aspetti che le classi non correlate implementino la tua interfaccia. Ad esempio, le interfacce Comparable e Cloneable sono implementate da molte classi non correlate.
  2. Si desidera specificare il comportamento di un particolare tipo di dati, ma non si preoccupa di chi implementa il suo comportamento.
  3. Vuoi approfittare di più eredità.

fonte

Le risposte variano tra le lingue. Ad esempio, in Java una class può implementare (ereditare da) più interfacce ma ereditare solo da una class astratta. Quindi le interfacce ti danno più flessibilità. Ma questo non è vero in C ++.

Penso che il modo più succinto di metterlo sia il seguente:

Proprietà condivise => class astratta.
Funzionalità condivisa => interfaccia.

E per dirla in modo meno sintetico …

Esempio di class astratta:

 public abstract class BaseAnimal { public int NumberOfLegs { get; set; } protected BaseAnimal(int numberOfLegs) { NumberOfLegs = numberOfLegs; } } public class Dog : BaseAnimal { public Dog() : base(4) { } } public class Human : BaseAnimal { public Human() : base(2) { } } 

Poiché gli animali hanno una proprietà condivisa – numero di gambe in questo caso – ha senso creare una class astratta contenente questa proprietà condivisa. Questo ci consente anche di scrivere codice comune che opera su quella proprietà. Per esempio:

 public static int CountAllLegs(List animals) { int legCount = 0; foreach (BaseAnimal animal in animals) { legCount += animal.NumberOfLegs; } return legCount; } 

Esempio di interfaccia:

 public interface IMakeSound { void MakeSound(); } public class Car : IMakeSound { public void MakeSound() => Console.WriteLine("Vroom!"); } public class Vuvuzela : IMakeSound { public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!"); } 

Nota che Vuvuzelas e Cars sono cose completamente diverse, ma hanno funzionalità condivise: fare un suono. Quindi, un’interfaccia ha senso qui. Inoltre, consentirà ai programmatori di raggruppare elementi che creano suoni insieme in un’interfaccia comune – IMakeSound in questo caso. Con questo design, puoi scrivere il seguente codice:

 List soundMakers = new List(); soundMakers.Add(new Car()); soundMakers.Add(new Vuvuzela()); soundMakers.Add(new Car()); soundMakers.Add(new Vuvuzela()); soundMakers.Add(new Vuvuzela()); foreach (IMakeSound soundMaker in soundMakers) { soundMaker.MakeSound(); } 

Puoi dire che cosa produrrebbe?

Infine, puoi combinare i due.

Esempio combinato:

 public interface IMakeSound { void MakeSound(); } public abstract class BaseAnimal : IMakeSound { public int NumberOfLegs { get; set; } protected BaseAnimal(int numberOfLegs) { NumberOfLegs = numberOfLegs; } public abstract void MakeSound(); } public class Cat : BaseAnimal { public Cat() : base(4) { } public override void MakeSound() => Console.WriteLine("Meow!"); } public class Human : BaseAnimal { public Human() : base(2) { } public override void MakeSound() => Console.WriteLine("Hello, world!"); } 

Qui, BaseAnimal tutti i BaseAnimal emettere un suono, ma non sappiamo ancora la sua implementazione. In tal caso, possiamo astrarre l’implementazione dell’interfaccia e debind la sua implementazione alle sue sottoclassi.

Un ultimo punto, ricorda come nell’esempio di class astratto siamo stati in grado di operare sulle proprietà condivise di diversi oggetti e nell’esempio di interfaccia siamo stati in grado di invocare la funzionalità condivisa di oggetti diversi? In questo ultimo esempio, potremmo fare entrambe le cose.

Utilizzare una class astratta se si desidera fornire alcune implementazioni di base.

in java puoi ereditare da una class (astratta) per “fornire” funzionalità e puoi implementare molte interfacce per “assicurare” la funzionalità

Puramente sulla base dell’ereditarietà, si utilizzerà un abstract in cui si definiscono chiaramente relazioni discendenti e astratte (es. Animale-> cat) e / o si richiede l’ereditarietà di proprietà virtuali o non pubbliche, in particolare lo stato condiviso (che le interfacce non supportano ).

Dovresti cercare di favorire la composizione (tramite l’integrazione delle dipendenze) sull’ereditarietà, laddove ansible, e notare che i contratti Interfaces supportano il test unitario, la separazione delle preoccupazioni e l’ereditarietà multipla (che varia la lingua) in un modo che gli Abstract non possono.

Una posizione interessante in cui le interfacce migliorano rispetto alle classi astratte è quando è necessario aggiungere funzionalità aggiuntive a un gruppo di oggetti (correlati o non correlati). Se non è ansible assegnare loro una class astratta di base (ad esempio, sono sealed o hanno già un genitore), è ansible fornire loro un’interfaccia fittizia (vuota) e quindi semplicemente scrivere i metodi di estensione per tale interfaccia.

Questa può essere una chiamata molto difficile da fare …

Un puntatore che posso dare: Un object può implementare molte interfacce, mentre un object può ereditare solo una class base (in un linguaggio OO moderno come c #, so che C ++ ha ereditarietà multipla – ma non è così malvisto?)

Una class astratta può avere implementazioni.

Un’interfaccia non ha implementazioni, semplicemente definisce un tipo di contratto.

Possono esserci anche alcune differenze dipendenti dalla lingua: ad esempio C # non ha ereditarietà multipla, ma è ansible implementare più interfacce in una class.

Per me, andrei con le interfacce in molti casi. Ma preferisco le lezioni astratte in alcuni casi.

Le classi in OO generaly si riferiscono all’implementazione. Uso classi astratte quando voglio forzare alcuni dettagli di implementazione per i childs con cui vado con le interfacce.

Ovviamente, le classi astratte sono utili non solo per forzare l’implementazione ma anche per condividere alcuni dettagli specifici tra molte classi correlate.

La regola del pollice base è: per “nomi” usa la class astratta e per “verbi” usa l’interfaccia

Ad esempio: la car è una class astratta e drive , possiamo farne un’interfaccia.