Questa risposta fa un buon lavoro nel spiegare le differenze tra una classe astratta e un'interfaccia, ma non risponde al motivo per cui dovresti dichiararne una.
Da un punto di vista puramente tecnico, non è mai necessario dichiarare una classe come astratta.
Considera le seguenti tre classi:
class Database {
public String[] getTableNames() { return null; } //or throw an exception? who knows...
}
class SqlDatabase extends Database { } //TODO: override getTableNames
class OracleDatabase extends Database { } //TODO: override getTableNames
Non è necessario rendere astratta la classe Database, anche se esiste un evidente problema con la sua implementazione: quando si scrive questo programma, è possibile digitare new Database()
e sarebbe valido, ma non funzionerebbe mai.
Indipendentemente da ciò, otterresti comunque il polimorfismo, quindi fino a quando il tuo programma fa solo istanze SqlDatabase
e OracleDatabase
, puoi scrivere metodi come:
public void printTableNames(Database database) {
String[] names = database.getTableNames();
}
Le classi astratte migliorano la situazione impedendo a uno sviluppatore di creare un'istanza della classe di base, poiché uno sviluppatore l'ha contrassegnata come priva di funzionalità . Fornisce inoltre sicurezza in fase di compilazione in modo da poter garantire che tutte le classi che estendono la tua classe astratta forniscano la minima funzionalità minima per funzionare, e non devi preoccuparti di mettere metodi stub (come quello sopra) che gli eredi hanno in qualche modo sapere magicamente che devono scavalcare un metodo per farlo funzionare.
Le interfacce sono un argomento totalmente separato. Un'interfaccia consente di descrivere quali operazioni possono essere eseguite su un oggetto. Normalmente useresti interfacce quando scrivi metodi, componenti, ecc. Che usano i servizi di altri componenti, oggetti, ma non ti importa quale sia il tipo reale di oggetto da cui stai ottenendo i servizi.
Considera il seguente metodo:
public void saveToDatabase(IProductDatabase database) {
database.addProduct(this.getName(), this.getPrice());
}
Non ti importa se l' database
oggetto eredita da un particolare oggetto, ti importa solo che abbia un addProduct
metodo. Quindi, in questo caso, un'interfaccia è più adatta che far sì che tutte le tue classi ereditino dalla stessa classe base.
A volte la combinazione dei due funziona molto bene. Per esempio:
abstract class RemoteDatabase implements IProductDatabase {
public abstract String[] connect();
public abstract void writeRow(string col1, string col2);
public void addProduct(String name, Double price) {
connect();
writeRow(name, price.toString());
}
}
class SqlDatabase extends RemoteDatabase {
//TODO override connect and writeRow
}
class OracleDatabase extends RemoteDatabase {
//TODO override connect and writeRow
}
class FileDatabase implements IProductDatabase {
public void addProduct(String name, Double price) {
//TODO: just write to file
}
}
Notare come alcuni database ereditano da RemoteDatabase per condividere alcune funzionalità (come la connessione prima di scrivere una riga), ma FileDatabase è una classe separata che implementa solo IProductDatabase
.