Questa risposta fa un buon lavoro di spiegazione delle differenze tra una classe astratta e un'interfaccia, ma non risponde perché dovresti dichiararne uno.
Da un punto di vista puramente tecnico, non esiste mai un requisito per 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 avere per rendere astratta la classe Database, anche se c'è un problema evidente con la sua implementazione: quando stai scrivendo questo programma, potresti digitare new Database()
e sarebbe valido, ma non funzionerebbe mai.
Indipendentemente, si otterrebbe comunque il polimorfismo, quindi se il tuo programma crea solo SqlDatabase
e OracleDatabase
istanze, potresti scrivere metodi come:
public void printTableNames(Database database) {
String[] names = database.getTableNames();
}
Le classi astratte migliorano la situazione impedendo ad uno sviluppatore di creare un'istanza della classe base, perché lo sviluppatore ha contrassegnato come mancante di funzionalità . Fornisce inoltre sicurezza in fase di compilazione in modo che tu possa garantire che tutte le classi che estendono la tua classe astratta forniscano la funzionalità minima necessaria per funzionare e non devi preoccuparti di mettere i metodi di stub (come il uno sopra) che gli eredi in qualche modo devono sapere magicamente che hanno di sovrascrivere un metodo per farlo funzionare.
Interfacce sono un argomento completamente separato. Un'interfaccia ti consente di descrivere quali operazioni possono essere eseguite su un oggetto. Normalmente utilizzi le interfacce quando scrivi metodi, componenti, ecc. Che usano i servizi di altri componenti, oggetti, ma non ti interessa quale sia l'effettivo tipo di oggetto da cui stai ricevendo i servizi.
Considera il seguente metodo:
public void saveToDatabase(IProductDatabase database) {
database.addProduct(this.getName(), this.getPrice());
}
Non ti importa se l'oggetto database
eredita da un particolare oggetto, ti interessa solo che abbia un metodo addProduct
. Quindi, in questo caso, un'interfaccia è più adatta rispetto a far sì che tutte le tue classi capiscano di ereditare dalla stessa classe base.
A volte la combinazione dei due funziona molto bene. Ad 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
}
}
Si noti come alcuni database ereditino da RemoteDatabase per condividere alcune funzionalità (come la connessione prima di scrivere una riga), ma FileDatabase è una classe separata che implementa solo IProductDatabase
.