Le classi astratte e le interfacce sono due concetti fondamentali della programmazione orientata agli oggetti (OOP, Object Oriented Programming), a prescindere dal linguaggio usato, sia questo Java, C++ o C#. L'importanza di conoscere i concetti teorici che stanno alla base di questi due elementi, è fondamentale per un buon programmatore; capire la differenza tra classi astratte ed interfacce permette di valutare in modo corretto quando usare l'una e quando usare l'altra.
Sia le classi astratte che le interfacce non possono essere instanziate poichè al loro interno definiscono soltanto i metodi e le proprietà che dovranno essere implementate dalle classi che deriveranno da esse, rappresentano pertanto un modello, oserei dire un "template" per le classi che una volta derivate da esse ed instanziate ne implementeranno il comportamento.
Classi astratte ed interfacce, così simili tra loro, possono confondere le idee ai neo programmatori che per le prime volte si avvicinano a questi concetti ed al polimorfismo della OOP, ma sebbene le similitudini sono molte vi sono delle sottili ma fondamentali differenze concettuali che è bene avere chiare in mente, per capire quando usare una classe astratta e quando una interfaccia.
Passiamo a descrivere brevemente prima le classi astratte e poi le interfacce, dopodichè ne evidenzieremo le differenze concettuali e di programmazione. Sebbene questi concetti teorici esulano dal linguaggio ae oggetti usato, come sintassi per i nostri esempi faremo riferimento al linguaggio C#, che è quello che ci piace di più ;)
Le Classi Astratte
Una classe astratta è una classe che non può essere instanziata e che serve solo per essere derivata, definendo al suo interno metodi e proprietà tutti o in parte anch'essi astratti. Una classe astratta che implementa solo ed esclusivamente metodi e proprietà astratte viene detta classe astratta pura.
Per definire una classe astratta in C#, dobbiamo anteporre alla definizione della classe la keyword abstract, stessa cosa vale per la definizione di metodi e proprietà astratte, come possiamo vedere nell'esempio seguente:
public abstract class Mammifero {
//metodo astratto
public abstract GetModoCamminare();
//proprietà astratta
public abstract string Name { get; set; }
...
}
Le classi astratte permetto d'implementare il concetto di polimorfismo, uno dei capisaldi della programmazione orientata agli ogetti. Il polimorfismo consente di assegnare comportamenti differenti a instanze di classi differenti devivanti tutte dalla stessa classe base, in questo caso la classe astratta. Per implementare il polimorfismo però i metodi e le proprietà del tipo base devono essere dichiarati come astratti o virtuali, affinchè questi possano essere riscritti (override) dal tipo derivato che ne instanzierà un particolare oggetto differente da un'altro ma con caratteristiche simili poichè derivanti entrambi dalla stessa classe madre, che ne definisce i comportamenti comuni tramite le signature dei metodi astratti.
Nel nostro esempio abbiamo definito una classe astratta Mammifero con un metodo modo di camminare ed una proprietà per il nome, le possibili instanze potrebbero essere:
public class Uomo: Mammifero {
//metodo astratto
public override string GetModoCamminare();
//proprietà astratta
public override string Name { get; set; }
...
}
Come notate, la classe uomo deriva da quella mammifero (classe astratta) e ne implementa i metodi e le proprietà astratte tramite override, per spiegare il concetto facciamo un banale esempio, la classe astratta definisce (ma non implementa) il metodo modo di camminare, nell'istanza della classe uomo questo metodo ritornerà bipede, mentre per un'istanza cane tornerebbe quadrupede, vedete il polimorfismo, a concetti comuni s'implementano comportamenti differenti.
Nota: è bene precisare che tra metodi astratti e virtuali vi è una sostanziale differenza, i primi vengono solamente definiti nella classe astratta ed ognuno di esso deve essere necessariamente implementato nella classe derivata, i metodi virtuali possono invece essere anche implementati nella classe astratta e se questi non vengono riscritti (override) nella classe derivata, questa implementerà il metodo (virtuale) della classe astratta. Si definisce un metodo virtuale in C#, mettendo la keyword virtual prima della definizione del metodo stesso.
Le Interfacce
Le interfacce sono molto simili alle classi astratte, in quanto anch'essa definisce metodi e proprietà astratte. Nelle interfacce non troveremo l'implemetazione di alcun metodo o proprietà, come per le classi astratte pure, si dice che le interfacce stipulino un contratto con la classe derivata che le implementa.
Per definire un'interfaccia in C#, bisogna anteporre la keyword interface al nome dell'interfaccia stessa, inoltre gli access modifier per i membri non vengono specificati nelle interfacce. Proviamo ad es. a definire un'interfaccia che definisca un metodo che sarà comune a tutte le sue classi derivate:
public interface ICalc {
//metodo dell'interfaccia
public Count();
...
}
In questo semplicissimo esempio abbiamo definito un solo metodo per l'interfaccia, ma queste possono definire non solo metodi, ma anche proprietà ed eventi.
Solitamente i nomi delle interfacce vengono preceduti da una i maiuscola proprio per identificarle immediatamente all'interno del codice e distinguerle dalle classi, per quanto riguarda la loro implementazione invece, questa è identica alle regole usate per l'ereditarietà tra le classi, quindi basta postporre i due punti ":" al nome della classe e scrivere di seguito il nome dell'interfaccia che si vuole implementare. Una prima differenza tra classi astratte ed interfacce, almeno in C# e Visual Basic, e che mentre per le classi non è ammessa l'ereditarietà multipla, ma soltanto quella singola, questo non è vero per le interfacce. Una classe derivata può implemetare più di una interfaccia contemporaneamente, basterà separare con una virgola i nomi delle interfacce che si vogliono implementare per quella classe. Ovviamente una classe può derivare contemporaneamente da una classe madre e da una o più interfacce basta separare i nomi dell'une e delle altre sempre con le virgole.
A differenza delle classi astratte per l'implementazione di una interfaccia non si rende necessario usare la keyword override, basterà implementare il metodo definito nella interfaccia.
Quando usare le classi astratte e quando le Interfacce
Come abbiamo visto, sia le classi astratte che le interfacce rappresentano un modello, un contratto che la classe derivata deve rispettare implementando i metodi e le proprietà definite nel tipo base, ma allora al di là delle piccole differenze di sintassi qual'è la sostanziale differenza tra classi astratte pure ed interfacce, come capire quando usare l'una e quando le altre? La risposta migliore è a seconda del tipo di contratto che la classe derivata deve implementare col tipo base, le classi astratte pure definiscono un legame più forte con la classe derivata poichè ne rappresentano il tipo base definendone il comportamento comune (vedi l'es. della classe mammifero). Mentre le interfacce possono essere usate per definire un modello generico, che implementa un comportamento comune a classi di vario genere e natura, ad esempio il metodo calc dell'iiterfaccia ICalc potrebbe essere comune sia ad un'istanza di una classe calcolatrice che a quello di un'istanza punto geometrico.
Se mi permettete una definizione personale, direi che le classi astratte pure definiscono un contratto di tipo verticale (dedicato) con le instanze delle classi figlie, mentre le interfacce rappresentano di più un contratto di tipo orizzontale (generico) con gli oggetti che le implementeranno.