Ta odpowiedź dobrze wyjaśnia różnice między klasą abstrakcyjną a interfejsem, ale nie odpowiada, dlaczego należy ją zadeklarować.
Z czysto technicznego punktu widzenia nigdy nie ma wymogu deklarowania klasy jako abstrakcyjnej.
Rozważ następujące trzy klasy:
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
Nie musisz robić abstrakcji klasy Database, nawet jeśli istnieje oczywisty problem z jej implementacją: kiedy piszesz ten program, możesz pisać new Database()
i będzie poprawny, ale nigdy nie zadziała.
Niezależnie od tego nadal zachorowałbyś na polimorfizm, więc dopóki twój program tworzy SqlDatabase
i OracleDatabase
instancje, możesz pisać metody takie jak:
public void printTableNames(Database database) {
String[] names = database.getTableNames();
}
Klasy abstrakcyjne poprawiają sytuację, uniemożliwiając programistom tworzenie instancji klasy podstawowej, ponieważ programista zaznaczył, że brakuje jej funkcjonalności . Zapewnia również bezpieczeństwo podczas kompilacji, dzięki czemu możesz zapewnić, że wszystkie klasy, które rozszerzają klasę abstrakcyjną, zapewniają absolutną minimalną funkcjonalność do działania, i nie musisz się martwić o wprowadzenie metod pośredniczących (takich jak ta powyżej), które dziedziczą w jakiś sposób aby magicznie wiedzieć, że muszą zastąpić metodę, aby zadziałała.
Interfejsy to zupełnie osobny temat. Interfejs pozwala opisać, jakie operacje można wykonać na obiekcie. Zazwyczaj używasz interfejsów podczas pisania metod, komponentów itp., Które korzystają z usług innych komponentów, obiektów, ale nie obchodzi cię, jaki jest rzeczywisty typ obiektu, z którego otrzymujesz usługi.
Rozważ następującą metodę:
public void saveToDatabase(IProductDatabase database) {
database.addProduct(this.getName(), this.getPrice());
}
Nie obchodzi cię, czy database
obiekt dziedziczy po jakimś konkretnym obiekcie, zależy ci tylko na tym, że ma on addProduct
metodę. W takim przypadku interfejs jest bardziej odpowiedni niż sprawienie, aby wszystkie twoje klasy dziedziczyły po tej samej klasie bazowej.
Czasami połączenie tych dwóch działa bardzo dobrze. Na przykład:
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
}
}
Zwróć uwagę, jak niektóre bazy danych dziedziczą po RemoteDatabase, aby dzielić się pewną funkcjonalnością (np. Łączenie się przed zapisaniem wiersza), ale FileDatabase to osobna klasa, która tylko się implementuje IProductDatabase
.