Essa resposta explica bem as diferenças entre uma classe abstrata e uma interface, mas não responde por que você deve declarar uma.
Do ponto de vista puramente técnico, nunca há a exigência de 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 precisa abstrair a classe Database, mesmo que exista um problema óbvio com a sua implementação: Ao escrever este programa, você poderia digitar new Database()
e seria válido, mas nunca funcionaria.
Independentemente disso, você ainda obteria polimorfismo; portanto, enquanto seu programa apenas cria SqlDatabase
e OracleDatabase
instâncias, você pode escrever métodos como:
public void printTableNames(Database database) {
String[] names = database.getTableNames();
}
As classes abstratas melhoram a situação impedindo que um desenvolvedor instancie a classe base, porque um desenvolvedor a marcou como tendo falta de funcionalidade . Ele também fornece segurança em tempo de compilação, para que você possa garantir que todas as classes que estendem sua classe abstrata forneçam a funcionalidade mínima necessária para o trabalho e não precise se preocupar em colocar métodos stub (como o acima) que os herdeiros de alguma forma tenham saber magicamente que eles precisam substituir um método para fazê-lo funcionar.
As 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 não se importa com o tipo de objeto real do qual 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 database
objeto é herdado de um objeto em particular, apenas se importa que ele tenha um addProduct
método. Portanto, nesse caso, uma interface é mais adequada do que fazer com que todas as suas classes sejam herdadas 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 conectar antes de escrever uma linha), mas o FileDatabase é uma classe separada que apenas implementa IProductDatabase
.