Quando utilizzare le classi astratte anziché le interfacce con i metodi di estensione in C #?


70

"Classe astratta" e "interfaccia" sono concetti simili, con l'interfaccia che è la più astratta delle due. Un fattore di differenziazione è che le classi astratte forniscono implementazioni di metodi per le classi derivate quando necessario. In C #, tuttavia, questo fattore di differenziazione è stato ridotto dalla recente introduzione di metodi di estensione, che consentono di fornire implementazioni per i metodi di interfaccia. Un altro fattore di differenziazione è che una classe può ereditare solo una classe astratta (cioè non c'è ereditarietà multipla), ma può implementare più interfacce. Ciò rende le interfacce meno restrittive e più flessibili. Quindi, in C #, quando dovremmo usare le classi astratte anziché le interfacce con i metodi di estensione?

Un esempio notevole del modello del metodo di interfaccia + estensione è LINQ, in cui viene fornita la funzionalità di query per qualsiasi tipo implementato IEnumerabletramite una moltitudine di metodi di estensione.


2
E possiamo usare 'Proprietà' anche nelle interfacce.
Gulshan,

1
Sembra una domanda specifica per C # piuttosto che una OOD generale. (La maggior parte delle altre lingue OO non ha metodi né proprietà di estensione.)
Péter Török


4
I metodi di estensione non risolvono del tutto il problema che risolvono le classi astratte, quindi la ragione non è diversa da quella di Java o di qualche altro linguaggio simile a ereditarietà singola.
Giobbe

3
La necessità di implementazioni di metodi di classe astratti non è stata ridotta dai metodi di estensione. I metodi di estensione non possono accedere ai campi dei membri privati, né possono essere dichiarati virtuali ed essere sovrascritti nelle classi derivate.
zooone9243,

Risposte:


63

Quando hai bisogno di una parte della classe da implementare. Il miglior esempio che ho usato è il modello di metodo del modello.

public abstract class SomethingDoer
{
    public void Do()
    {
        this.DoThis();
        this.DoThat();
    }

    protected abstract void DoThis();
    protected abstract void DoThat();
}

Pertanto, è possibile definire i passaggi che verranno eseguiti quando viene chiamato Do (), senza conoscere i dettagli su come verranno implementati. Le classi di derivazione devono implementare i metodi astratti, ma non il metodo Do ().

I metodi di estensione non soddisfano necessariamente la parte "deve essere una parte della classe" dell'equazione. Inoltre, iirc, i metodi di estensione non possono (sembrano) essere di portata pubblica.

modificare

La domanda è più interessante di quanto inizialmente mi fosse stato dato credito. Dopo un ulteriore esame, Jon Skeet ha risposto a una domanda del genere su SO a favore dell'uso di interfacce + metodi di estensione. Inoltre, un potenziale aspetto negativo sta usando la riflessione su una gerarchia di oggetti progettata in questo modo.

Personalmente, ho difficoltà a vedere il beneficio di alterare una pratica attualmente comune, ma vedo anche pochi o nessun aspetto negativo nel farlo.

Va notato che è possibile programmare in questo modo in molte lingue tramite le classi Utility. Le estensioni forniscono solo lo zucchero sintattico per far sembrare che i metodi appartengano alla classe.


Sconfiggimi ...
Giorgi,

1
Ma la stessa cosa può essere fatta con interfacce e metodi di estensione. Lì i metodi che stai definendo nella classe base astratta Do()saranno definiti come metodo di estensione. E i metodi lasciati per la definizione delle classi derivate DoThis()saranno membri dell'interfaccia principale. E con le interfacce, puoi supportare più implementazioni / ereditarietà. E guarda questo- odetocode.com/blogs/scott/archive/2009/10/05/…
Gulshan

@Gulshan: Perché una cosa può essere fatta in più di un modo non rende invalido il modo scelto. Non vi è alcun vantaggio nell'uso delle interfacce. La stessa classe astratta è l'interfaccia. Non hai bisogno di interfacce per supportare più implementazioni.
ThomasX,

Inoltre, ci possono essere degli svantaggi nell'interfaccia + modello di estensione. Prendi la libreria Linq, per esempio. È fantastico, ma se devi usarlo solo una volta in quel file di codice, l'intera libreria Linq sarà disponibile in IntelliSense su OGNI IEnumerable nel tuo file di codice. Ciò può rallentare notevolmente le prestazioni dell'IDE.
KeithS,

-1 perché non hai dimostrato in alcun modo in che modo le classi astratte si adattano meglio al metodo Template rispetto alle interfacce
Benjamin Hodgson,

25

È tutto su ciò che vuoi modellare:

Le classi astratte funzionano per eredità . Essendo classi di base solo speciali, modellano alcuni is-a -relationship.

Ad esempio, un cane è un animale, quindi abbiamo

class Dog : Animal { ... }

Dato che non ha senso creare un generico animale generico (come sarebbe?), Creiamo questa Animalclasse base abstract- ma è ancora una classe base. E la Dogclasse non ha senso senza essere un Animal.

Le interfacce invece sono una storia diversa. Non usano l'ereditarietà ma forniscono polimorfismo (che può essere implementato anche con l'ereditarietà). Non modellano una relazione is-a , ma più di una supporta .

Prendi ad esempio IComparableun oggetto che supporta il confronto con un altro .

La funzionalità di una classe non dipende dalle interfacce che implementa, l'interfaccia fornisce semplicemente un modo generico di accedere alla funzionalità. Potremmo ancora fare Disposea Graphicso a FileStreamsenza averli implementati IDisposable. L' interfaccia collega semplicemente i metodi insieme.

In linea di principio, si potrebbero aggiungere e rimuovere interfacce proprio come le estensioni senza modificare il comportamento di una classe, arricchendo semplicemente l'accesso ad essa. Non puoi però togliere una classe di base poiché l'oggetto diventerebbe insignificante!


Quando sceglieresti di mantenere un abstract della classe base?
Gulshan,

1
@Gulshan: In caso affermativo, per qualche motivo, non ha senso creare effettivamente un'istanza della classe base. Puoi "creare" un Chihuahua o uno squalo bianco, ma non puoi creare un animale senza ulteriore specializzazione, quindi faresti Animalastratto. Ciò consente di scrivere abstractfunzioni specializzate dalle classi derivate. O più in termini di programmazione - probabilmente non ha senso creare un UserControl, ma come Button è un UserControl, quindi abbiamo lo stesso tipo di relazione classe / baseclass.
Dario,

se hai metodi astratti, allora hai classe astratta. E metodi astratti che hai quando non c'è valore per implementarli (come "go" per gli animali). E ovviamente a volte non vuoi permettere oggetti di qualche classe generale, che lo rendi astratto.
Dainius,

Non esiste una regola che dica che se è grammaticalmente e semanticamente significativo dire "B è un A", allora A deve essere una classe astratta. Dovresti preferire le interfacce fino a quando non puoi davvero evitare la classe. La condivisione del codice può essere effettuata tramite la composizione di interfacce, ecc. Rende più facile evitare di dipingersi in un angolo con strani alberi ereditari.
Sara,

3

Ho appena realizzato che non uso classi astratte (almeno non create) da anni.

La classe astratta è una strana bestia tra interfaccia e implementazione. C'è qualcosa nelle classi astratte che pizzica il mio senso del ragno. Direi che non si dovrebbe "esporre" l'implementazione dietro l'interfaccia in alcun modo (o fare alcun collegamento).

Anche se è necessario un comportamento comune tra le classi che implementano un'interfaccia, utilizzerei una classe di supporto indipendente, piuttosto che una classe astratta.

Vorrei andare alle interfacce.


2
-1: Non sono sicuro di quanti anni hai, ma potresti voler imparare i modelli di progettazione, in particolare il modello di metodo del modello. I modelli di progettazione ti renderanno un programmatore migliore e metteranno fine alla tua ignoranza delle lezioni astratte.
Jim G.

Lo stesso per la sensazione di formicolio. Le classi, la prima e principale funzionalità di C # e Java, sono una funzione per creare un tipo e concatenarlo immediatamente per sempre a un'implementazione.
Jesse Millikan,

2

IMHO, penso che ci sia un mix di concetti qui.

Le classi usano un certo tipo di eredità, mentre le interfacce usano un diverso tipo di eredità.

Entrambi i tipi di eredità sono importanti e utili e ognuno ha i suoi pro e contro.

Cominciamo dall'inizio.

La storia con le interfacce inizia molto tempo fa con il C ++. A metà degli anni '80, quando fu sviluppato il C ++, l'idea di interfacce come un diverso tipo di tipo non era ancora stata realizzata (sarebbe arrivata in tempo).

Il C ++ aveva solo un tipo di eredità, il tipo di eredità usato dalle classi, chiamato ereditarietà dell'implementazione.

Per poter applicare la raccomandazione GoF Book: "Per programmare per l'interfaccia e non per l'implementazione", C ++ ha supportato le classi astratte e l'ereditarietà dell'implementazione multipla per essere in grado di fare ciò che le interfacce in C # sono in grado (e utili!) Di fare .

C # introduce un nuovo tipo di tipo, interfacce e un nuovo tipo di ereditarietà, ereditarietà di interfaccia, per offrire almeno due modi diversi di supportare "Programmare per l'interfaccia e non per l'implementazione", con un avvertimento importante: solo C # supporta l'ereditarietà multipla con l'ereditarietà dell'interfaccia (e non con l'ereditarietà dell'implementazione).

Quindi, i motivi architettonici alla base delle interfacce in C # sono la raccomandazione GoF.

Spero che questo possa essere d'aiuto.

Cordiali saluti, Gastón


1

L'applicazione di metodi di estensione a un'interfaccia è utile per applicare comportamenti comuni tra classi che possono condividere solo un'interfaccia comune. Tali classi possono già esistere ed essere chiuse alle estensioni con altri mezzi.

Per i nuovi progetti preferirei l'uso di metodi di estensione sulle interfacce. Per una gerarchia di tipi, i metodi di estensione forniscono un mezzo per estendere il comportamento senza dover aprire l'intera gerarchia per la modifica.

Tuttavia, i metodi di estensione non sono in grado di accedere all'implementazione privata, quindi nella parte superiore di una gerarchia, se dovessi incapsulare l'implementazione privata dietro un'interfaccia pubblica, una classe astratta sarebbe l'unico modo.


No, non è l'unico modo sicuro. C'è un altro modo più sicuro. Questo è il metodo di estensione. I clienti non saranno affatto infetti. Ecco perché ho citato interfacce + metodi di estensione.
Gulshan,

@Ed James, penso "less potenti" = "più flessibile" Quando sceglieresti una classe astratta più potente invece di un'interfaccia più flessibile?
Gulshan,

@Ed James In asp.mvc sono stati utilizzati sin dall'inizio metodi di estensione. Cosa ne pensi di questo?
Gulshan,

0

Penso che in C # l'ereditarietà multipla non sia supportata ed è per questo che il concetto di interfaccia è stato introdotto in C #.

Nel caso di classi astratte, l'ereditarietà multipla non è supportata. Quindi è facile decidere se usare classi o interfacce astratte.


Per essere precisi, sono le classi pure astratte che sono chiamate "interfacce" in C #.
Johan Boulé,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.