Esta resposta explica bem as diferenças entre uma classe abstrata e uma interface, mas ela não responde por que você deve declarar uma.
Do ponto de vista puramente técnico, nunca há um requisito para declarar uma classe como abstrata.
Considere as três classes a seguir:
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
Você não tem para tornar a classe Database abstrata, mesmo que exista um problema óbvio com a sua implementação: Quando você está escrevendo este programa, você pode digitar new Database()
e ele será válido, mas nunca funcionaria.
Independentemente disso, você ainda obteria polimorfismo, portanto, contanto que seu programa crie apenas SqlDatabase
e OracleDatabase
instâncias, você poderia escrever métodos como:
public void printTableNames(Database database) {
String[] names = database.getTableNames();
}
As classes abstratas melhoram a situação evitando que um desenvolvedor instancie a classe base, porque um desenvolvedor a marcou como tendo funcionalidade ausente . Ele também fornece segurança em tempo de compilação para que você possa garantir que quaisquer classes que estendam sua classe abstrata forneçam a funcionalidade mínima para funcionar, e você não precisa se preocupar em colocar métodos stub (como o um acima) que os herdeiros de alguma forma têm que magicamente saber que eles têm para substituir um método para que ele funcione.
Interfaces são um tópico totalmente separado. Uma interface permite descrever quais operações podem ser executadas em um objeto. Você normalmente usaria interfaces ao escrever métodos, componentes, etc., que usam os serviços de outros componentes, objetos, mas você não se importa com o tipo real de objeto do qual você está obtendo os serviços.
Considere o seguinte método:
public void saveToDatabase(IProductDatabase database) {
database.addProduct(this.getName(), this.getPrice());
}
Você não se importa se o objeto database
herda de qualquer objeto em particular, apenas se importa que ele tenha um método addProduct
. Portanto, nesse caso, uma interface é mais adequada do que fazer todas as suas classes herdarem da mesma classe base.
Às vezes, a combinação dos dois funciona muito bem. Por exemplo:
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 como alguns dos bancos de dados herdam do RemoteDatabase para compartilhar algumas funcionalidades (como se conectar antes de gravar uma linha), mas FileDatabase é uma classe separada que implementa apenas IProductDatabase
.