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 existe el requisito de declarar una clase como abstracta.
Considere 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
Usted no tiene que hacer la clase de base de datos abstracto, a pesar de que existe un problema evidente con su aplicación: Al escribir este programa, puede escribir new Database()
y sería válido, pero que nunca funcionaría.
De todos modos, aún obtendría polimorfismo, por lo que mientras su programa solo tenga instancias SqlDatabase
y OracleDatabase
ejemplos, podría 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 instancias de la clase base, porque un desarrollador la ha marcado como faltante de funcionalidad . También proporciona seguridad en tiempo de compilación para que pueda asegurarse de que cualquier clase que amplíe su clase abstracta brinde la funcionalidad mínima básica para trabajar, y no tenga que preocuparse por poner métodos de código auxiliar (como el anterior) que los herederos de alguna manera tienen para saber mágicamente que tienen que anular un método para que funcione.
Las interfaces son un tema totalmente separado. Una interfaz le permite describir qué operaciones se pueden realizar en un objeto. Normalmente usaría interfaces al escribir métodos, componentes, etc. que usan los servicios de otros componentes, objetos, pero no le importa cuál es el tipo real de objeto del que obtiene los servicios.
Considere el siguiente método:
public void saveToDatabase(IProductDatabase database) {
database.addProduct(this.getName(), this.getPrice());
}
No le importa si el database
objeto hereda de un objeto en particular, solo le importa que tenga un addProduct
método. Entonces, en este caso, una interfaz es más adecuada que hacer que todas sus clases 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 se implementa IProductDatabase
.