Ця відповідь добре допомагає пояснити відмінності між абстрактним класом та інтерфейсом, але не відповідає, чому слід оголосити його.
З чисто технічної точки зору, ніколи не існує вимоги оголошувати клас абстрактним.
Розглянемо наступні три класи:
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
Вам не доведеться робити клас баз даних абстрактним, хоча існує очевидна проблема з його реалізацією: Коли ви пишете цю програму, ви можете ввести 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
.