Esta respuesta hace un buen trabajo al explicar las diferencias entre una clase abstracta y una interfaz, pero no responde por qué debería declarar una.
Desde un punto de vista puramente técnico, nunca hay un requisito para declarar una clase como abstracta.
Considera las siguientes tres clases:
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
No tienes para hacer que la clase Base de datos sea abstracta, aunque hay un problema obvio con su implementación: cuando escribes este programa, puedes escribir new Database()
y sería válido, pero nunca funcionaría.
En cualquier caso, todavía obtendrías polimorfismo, por lo que siempre que tu programa solo genere las instancias SqlDatabase
y OracleDatabase
, podrías escribir métodos como:
public void printTableNames(Database database) {
String[] names = database.getTableNames();
}
Las clases abstractas mejoran la situación al evitar que un desarrollador cree una instancia de la clase base, porque un desarrollador lo ha marcado como que le falta funcionalidad . También proporciona seguridad en tiempo de compilación para que pueda asegurarse de que cualquier clase que extienda su clase abstracta proporcione la funcionalidad mínima para que funcione, y no debe preocuparse por poner métodos de código auxiliar (como el uno arriba) que los herederos de alguna manera deben saber mágicamente que tienen para anular un método para que funcione.
Interfaces son un tema totalmente separado. Una interfaz le permite describir qué operaciones se pueden realizar en un objeto. Normalmente, usará interfaces al escribir métodos, componentes, etc. que utilizan los servicios de otros componentes, objetos, pero no le importa de qué tipo de objeto real recibe los servicios.
Considera el siguiente método:
public void saveToDatabase(IProductDatabase database) {
database.addProduct(this.getName(), this.getPrice());
}
No te importa si el objeto database
hereda de un objeto en particular, solo te importa que tenga el método addProduct
. Entonces, en este caso, una interfaz es más adecuada que hacer que todas sus clases se hereden de la misma clase base.
A veces la combinación de los dos funciona muy bien. Por ejemplo:
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
}
}
Observe cómo algunas de las bases de datos heredan de RemoteDatabase para compartir alguna funcionalidad (como conectarse antes de escribir una fila), pero FileDatabase es una clase separada que solo implementa IProductDatabase
.