Dlaczego powinienem zadeklarować klasę jako klasę abstrakcyjną?

40

Znam składnię, reguły zastosowane do klasy abstrakcyjnej i chcę znać użycie klasy abstrakcyjnej

Klasy abstrakcyjnej nie można utworzyć bezpośrednio, ale można ją rozszerzyć o inną klasę

Jaka jest z tego korzyść?

Czym różni się od interfejsu?

Wiem, że jedna klasa może implementować wiele interfejsów, ale może rozszerzyć tylko jedną klasę abstrakcyjną. Czy to tylko różnica między interfejsem a klasą abstrakcyjną?

Mam świadomość korzystania z interfejsu. Nauczyłem się tego z modelu delegowania zdarzeń AWT w Javie.

W jakich sytuacjach powinienem zadeklarować klasę jako klasę abstrakcyjną? Jakie są z tego korzyści?

Vaibhav Jani
źródło
14
Pytanie zostało zadane. Wyszukiwanie wyświetli inne pytania takie jak to. Powinieneś zacząć od Google, co doprowadzi cię do przepełnienia stosu. Wszystkie są duplikatami: stackoverflow.com/search?q=abstract+interface .
S.Lott,
1
„korzystanie z klasy Abstract po prostu omawiają ... zasady”. Co? Czym różnią się „reguły” od „użytkowania”? Nawiasem mówiąc, zadano ci dokładne pytanie. Wiem. Odpowiedziałem na to. Szukaj dalej. Ważne jest, aby nauczyć się korzystać z wyszukiwania.
S.Lott,
Mam na myśli „W jakich sytuacjach powinienem zadeklarować klasę jako klasę abstrakcyjną? Jakie są z tego korzyści?”
Vaibhav Jani
Interfejs to czysta klasa abstrakcyjna, to wszystko. Są jednym w tym samym. Cały czas używam klasy abstrakcyjnej dla funkcji w klasie podstawowej, której nie można zaimplementować, ponieważ potrzebuje ona danych, które mają tylko podklasy, ale chcę się upewnić, że każda podklasa ma tę funkcję i odpowiednio ją implementuje.
AngryBird
Uważam, że wzorzec metody szablonu jest bardzo wydajny i stanowi dobry przypadek użycia dla klas abstrakcyjnych.
m3th0dman

Odpowiedzi:

49

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 SqlDatabasei OracleDatabaseinstancje, 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 databaseobiekt dziedziczy po jakimś konkretnym obiekcie, zależy ci tylko na tym, że ma on addProductmetodę. 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.

Kevin McCormick
źródło
16

Podobieństwa

Klasy abstrakcyjne i interfejsy są wymagane do abstrakcji. Nie można ich utworzyć za pomocą nowego , ale można je rozwiązać poprzez odwrócenie kontenerów kontrolnych lub wzorce fabryczne.

Różnica

  1. Interfejsy

    • Zdefiniuj dobrze znany kontrakt publiczny, umiejętności tego typu
    • Ma zastosowanie do pokazania dziedziczenia poziomego, tj. Rozgałęzienia na pierwszym poziomie dziedziczenia (np. ILog w celu zdefiniowania funkcji rejestrowania w bazie danych, pliku tekstowym, XML, SOAP itp.)
    • Wszyscy członkowie są publiczni
    • Żadne wdrożenie nie jest dozwolone
    • Dziecko dziedziczące może mieć wiele interfejsów do zaimplementowania
    • Przydatny do integracji stron trzecich
    • Nazewnictwo zwykle zaczyna się od I
  2. Klasa abstrakcyjna

    • Zdefiniuj strukturę, tożsamość i niektóre domyślne obsługiwane zachowania
    • Ma zastosowanie do wykazywania dziedziczenia pionowego, tj. Głębokiego rozgałęzienia na kilku poziomach (np. Klasa AbstractEntity w programowaniu opartym na domenie)
    • Członkowie mogą mieć różną widoczność (od publicznej do prywatnej)
    • Możesz zaimplementować niektórych członków (np. * Klasy Reader)
    • Dziecko dziedziczące może mieć tylko jedną podstawową klasę abstrakcyjną

Naprawdę łatwo jest znaleźć odpowiedź za pomocą prostego zapytania Google .

oleksii
źródło
co robisz według implementacji niedozwolone? klasa java może implementować interfejsy.
Sajuuk
@Sajuuk ten wiersz odnosi się do interfejsu. Nie można umieścić implementacji umowy w interfejsie. Możesz mieć domyślną implementację kontraktu w klasie abstrakcyjnej.
oleksii
11

Czym różni się od interfejsu?

W klasie abstrakcyjnej możesz zaimplementować niektóre metody i pozostawić (wymusić) resztę implementacji przez klasę rozszerzającą. Nie można zaimplementować metod w interfejsie. Nie możesz zmusić nikogo do zastąpienia czegokolwiek przy rozszerzaniu zwykłej klasy. Dzięki klasie abstrakcyjnej możesz.

Joonas Pulakka
źródło
To naprawdę wymaga aktualizacji Java 8, w której wprowadzono domyślne metody.
Haakon Løtveit
8

Klasy abstrakcyjne są dla relacji „jest to”, a interfejsy są przeznaczone dla „można zrobić”.

Klasy abstrakcyjne pozwalają dodawać podstawowe zachowania, dzięki czemu programiści nie muszą kodować wszystkiego, a jednocześnie zmuszają ich do podążania za twoim projektem.

Tulains Córdova
źródło
3

Oprócz głębokich szczegółów technicznych - takich jak implementacja niektórych metod dla klas abstrakcyjnych itp., Znaczenie jest takie:

Interfejsy definiują wspólne możliwości - IEnumerable określa, że ​​klasa, która implementuje ten interfejs, może zostać wyliczona. Nie mówi nic o samej klasie.

Klasy abstrakcyjne (lub podstawowe) definiują zachowanie - WebRequest definiuje wspólne zachowanie wszystkich klas potomnych, takich jak HttpWebRequest itp. Definiuje podstawowe znaczenie klasy i jej prawdziwy cel - dostęp do zasobów sieciowych.

Słoneczny
źródło
2

Wpis w Wikipedii .

Główne różnice między interfejsem a klasą abstrakcyjną polega na tym, że klasa abstrakcyjna może zapewniać zaimplementowane metody. Dzięki interfejsom możesz deklarować tylko metody, pisać ich podpis. Oto przykład klasy, która rozszerza klasę abstrakcyjną, która implementuje dwa interfejsy: (java)

interface MyInterface1 {
  string getValue1();
}

interface MyInterface2 {
  string getValue2();
}

abstract class MyAbstractClass implements MyInterface1, MyInterface2{
  void printValues() {
    System.out.println("Value 1: " + getValue1() + ", Value 2: " + getValue2() + 
                       ", Value 3: " + getValue3());
  }

  protected abstract string getValue3();
}

class ImpClass extends MyAbstractClass {
  public string getValue1() {
    return "1";
  }

  public string getValue2() {
    return "2";
  }

  protected string getValue3() {
    return "3";
  }
}

W tym przykładzie MyAbstractClass udostępnia metodę publiczną, która drukuje wszystkie trzy wartości. W ImpClass musisz zaimplementować odpowiednio getValue1 i getValue2 z MyInterface1 i MyInterface2 oraz getValue3 z klasy abstrakcyjnej.

Voilà.

Jest więcej aspektów (interfejs: tylko metody publiczne, klasa abstrakcyjna: chronione streszczenie i publiczne metody abstrakcyjne), ale możesz to przeczytać sam.

Na koniec, klasa abstrakcyjna, która zapewnia jedynie metody abstrakcyjne, jest „czystą” abstrakcyjną klasą bazową, czyli interfejsem.

Jalayn
źródło
2
  • Interfejs - gdy kilka klas współdzieli interfejs API (nazwy metod i parametry)
  • Klasa abstrakcyjna - gdy kilka klas ma ten sam kod (implementacja)

Innymi słowy, powinieneś zacząć od pytania: „czy te klasy koniecznie współużytkują implementację , czy tylko mają wspólny interfejs ?”

Jeśli odpowiedź jest mieszana, na przykład - te trzy klasy muszą współużytkować implementację, ale te dwie pozostałe współużytkują tylko interfejs API - możesz stworzyć interfejs dla wszystkich pięciu z nich i klasę abstrakcyjną dla tych trzech ze wspólnym kod.

Istnieją również inne sposoby udostępniania implementacji, na przykład enkapsulowanie obiektu za pomocą tej implementacji (np. We wzorze strategii ).

Viliam Búr
źródło
1

Zadeklarujesz streszczenie klasy, jeśli nie chcesz, aby programista (prawdopodobnie sam) mógł go utworzyć, ponieważ to nie działałoby lub nie miałoby sensu.

Rozważmy na przykład grę, w której istnieją różne typy elementów gry. Wszystkie dziedziczą od GameEntityklasy podstawowej .

abstract class GameEntity{

    int lifePoint, speed, damage;

    public attack(GameEntity target){ target.damage(damage); }

    public damage(int damageInflicted){ lifePoint -= damageInflicted - speed; }

    // etc...

}

Ta klasa jest zadeklarowana, abstractponieważ nie ma sensu jej tworzyć. Deklaruje niektóre akcje dla elementów gry i niektóre atrybuty, ale nigdzie w tej klasie te atrybuty nie są inicjowane. Ta klasa służy jako szablon dla elementów gry, ale nie jest przeznaczona do samodzielnego tworzenia i jako taka deklarowana abstract.

Jeśli chodzi o różnicę w użyciu między klasą abstrakcyjną a interfejsem:

Moim zdaniem interfejs jest sposobem na uzyskanie zachowania polimorficznego bez ograniczenia przez mechanizm pojedynczego dziedziczenia niektórych języków.

Wróćmy do gry jako przykład. Rozważ klasę Enemypochodną GameEntity. Ta klasa ma metodę attackMeFromDistance(RangedAttacker attacker). Ta metoda ma na celu umożliwić bytom atakowanie wroga z daleka.

Jak widać, ta metoda przyjmuje RangedAttackertyp jako parametr. Jednak wszystkie jednostki gry już dziedziczą GameEntity. Nie mogą przedłużyć kolejnej klasy.

Weź udział w zajęciach Magei Archerna przykład. Chcemy zezwolić, aby oba z nich były akceptowane jako parametry w attackMeFromDistance(RangedAttacker attacker)metodzie, ale już one pochodzą GameEntity.

Aby rozwiązać ten problem, tworzymy nowy interfejs:

interface RangedAttacker{
    public void attackFromDistance();
}

Klasa, która implementuje ten interfejs, musi implementować attackFromDistance()metodę, dlatego jest zapewnione, że ma ona możliwości ataku dystansowego. Oznacza to, że attackMeFromDistancemetoda może teraz bezpiecznie akceptować klasy implementujące ten interfejs. Tak więc tworzenie Magei Archerimplementacja tego interfejsu rozwiązuje nasz problem.

Dla mnie jest to siła interfejsów.

Podsumowując, zwykle używasz klasy abstrakcyjnej, gdy chcesz mieć klasę bazową dla niektórych klas, ale nie ma sensu tworzyć jej samodzielnie (lub w przypadku, gdy ma abstractmetody, które muszą być zaimplementowane przez podklasy, a w tym przypadku kompilator wymusi utworzenie klasy abstract). Można użyć interfejsu, aby uzyskać zachowanie polimorficzne bez ograniczenia przez mechanizm pojedynczego dziedziczenia.

Aviv Cohn
źródło
0
  1. Jest szansa, że ​​zbyt mało metod jest wspólnych dla niektórych klas (logika biznesowa). Pozostałe metody są różne. W tego rodzaju scenariuszach możesz zaimplementować wszystkie popularne metody w jednej klasie, a pozostałe zadeklarować jako abstrakcyjne. Następnie powinieneś zadeklarować klasę jako abstrakcyjną.
  2. Czasami nie pozwalasz na tworzenie obiektu bezpośrednio w klasie. Tego rodzaju klasę należy zadeklarować jako abstrakcyjną.
Dharani Kumar
źródło