Этот ответ хорошо объясняет различия между абстрактным классом и интерфейсом, но он не отвечает, почему вы должны объявить его.
С чисто технической точки зрения никогда не требуется объявлять класс абстрактным.
Рассмотрим следующие три класса:
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
Вам не нужно делать класс Database абстрактным, даже если есть очевидная проблема с его реализацией: когда вы пишете эту программу, вы можете печатать, new Database()
и она будет действительной, но она никогда не будет работать.
В любом случае, вы все равно получите полиморфизм, поэтому, пока ваша программа только создает SqlDatabase
и создает OracleDatabase
экземпляры, вы можете написать такие методы:
public void printTableNames(Database database) {
String[] names = database.getTableNames();
}
Абстрактные классы улучшают ситуацию, не давая разработчику создавать базовый класс, потому что разработчик пометил его как отсутствующий функционал . Он также обеспечивает безопасность во время компиляции, так что вы можете гарантировать, что любые классы, которые расширяют ваш абстрактный класс, обеспечивают минимальную функциональность для работы, и вам не нужно беспокоиться о том, чтобы использовать методы-заглушки (как вышеописанные), которые каким-либо образом имеют наследники. волшебно знать, что они должны переопределить метод, чтобы заставить его работать.
Интерфейсы - это совершенно отдельная тема. Интерфейс позволяет вам описать, какие операции могут быть выполнены над объектом. Обычно вы используете интерфейсы при написании методов, компонентов и т. Д., Которые используют сервисы других компонентов, объектов, но вас не волнует, какой тип объекта вы получаете от сервисов.
Рассмотрим следующий метод:
public void saveToDatabase(IProductDatabase database) {
database.addProduct(this.getName(), this.getPrice());
}
Вам не важно, database
наследует ли объект какой-либо конкретный объект, вы просто заботитесь о том, чтобы у него был addProduct
метод. Так что в этом случае интерфейс лучше подходит, чем когда все ваши классы наследуются от одного базового класса.
Иногда комбинация двух работает очень хорошо. Например:
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
}
}
Обратите внимание, что некоторые базы данных наследуются от RemoteDatabase, чтобы использовать некоторые функциональные возможности (например, подключение перед записью строки), но FileDatabase - это отдельный класс, который реализует только IProductDatabase
.