Zrozumieć ideę klasy abstrakcyjnej

0

Cześć!
Od niedawna zacząłem uczyć się Javy. Obecnie przyswajane przeze mnie zagadnienie to klasa abstrakcyjna.

Nie mogę załapać idei istnienia klasy abstrakcyjnej.

Załóżmy, że mamy klasę ogólną samochód i klasy pochodne, dziedziczące po niej: osobowy, ciężarowy .
Z tego co zrozumiałem, to nie można stworzyć obiektu klasy abstrakcyjnej - po niej się tylko dziedziczy. Ale jaka to różnica, czy można stworzyć instancję tej klasy, czy nie?

Np. w wywołaniu:

osobowy ford = new osobowy()            //co to zmienia ?

Będę wdzięczny za obrazowe wyjaśnienie

0

Generalnie klasa abstrakcyjna jest pewnym ograniczeniem jaki stawiamy na kod, czyli w tym przypadku blokada na tworzenie obiektów tejże klasy. Po co tak? W zasadzie ma to 2 powody. Pierwszy to taki, by nie robić obiektów, które w rzeczywistości są niekompletne. Np. możesz mieć klasę figura - lecz ona sama w sobie nie odpowiada na ważne pytania - jak liczyć pole? jak ją rysować? Wiemy, że to figura, że ma współrzędna. No, ale klasa prosi się o wiele informacji jakie uzupełnimy w klasach pochodnych. Drugi przypadek to taki, by poprzez abstrakcyjne metody wymusić na użytkowniku implementację. Np. dziedzicząc po klasie figura, otrzymujemy abstrakcyjną metodę render. Jeśli jej nie zaimplementujesz to twoja klasa będzie do niczego więc robisz to o co Cię proszą twórcy klasy abstrakcyjnej.

Takie wymuszanie to środek twórców bibliotek czy frameworków do informowania programistów o tym co muszą jeszcze zrobić, by ich kod w końcu działał. Osobiście myślę, że klasy abstrakcyjne są tak naprawdę zbędne, bo służą tylko i wyłącznie po to, żeby głupi programista nie zrobił sobie krzywdy.

0

ale możesz mieć:
samochód[] tb={ new osobowy(), new ciężarowy() };
czyli możesz w jednej tablice mieć cały park samochodów a przy tym masz gwarancje że przez nieuwagę nie zrobisz coś takiego:
samochód[] tb={ new osobowy(), new ciężarowy(), new samochód() }; - tu kompilator będzie protestować bo samochód jest abstrakcyjny.

0

Generalnie tagowanie klas jako abstrakcyjne jednocześnie nie mając żadnych abstrakcyjnych metod jest dość rzadko spotykane, takie Yeti.

Natomiast jeśli klasa ma metody abstrakcyjne to musi być abstrakcyjna. Taka jest specyfikacja języka. I sens istnienia klas abstrakcyjnych jest ściśle powiązany z sensem istnienia metod abstrakcyjnych. W szczególności interfejsy w Javie to prawie to samo co klasy abstrakcyjne bez pól i z samymi metodami abstrakcyjnymi - jedyna różnica jest taka, że implementować można wiele interfejsów, natomiast nie można rozszerzać wielu abstrakcyjnych klas naraz.

Na upartego klasę abstrakcyjną można traktować jako coś pośredniego między klasą konkretną, a interfejsem.

0

Dzięki. Właśnie o takie wyjaśnienie mi chodziło.

Mam jednak jeszcze jedno pytanie. Poprzez implementację abstrakcyjnych metod wymuszamy na użytkowniku implementowanie ich w klasach pochodnych - to rozumiem. Ale czy metody abstrakcyjne mają jeszcze jakiś inny sens?
Np. Jeśli sam piszę program to wiem, że klasa Osobowy miasto mieć metodę Jedz() tak samo jak klasa Ciężarowy. Nie potrzebuję przecież abstrakcyjnej metody w klasie Samochód?

Miało być :

nowacki napisał(a):

... musi mieć metodę Jedz()...

0

Tu nie chodzi stricte o wymuszanie czegoś, a bardziej o to, że na wysokim poziomie chcesz np zaimplementować pewną wspólną metodę, ale ta metoda wykorzystuje funkcjonalności, które trzeba zaimplementować dopiero w klasach pochodnych.

Taki przykład z czapy:
Klasa bazowa abstrakcyjna Samochód ma właściwość limitWagowy, metodę abstrakcyjną policzWagę, oraz metodę nieabstrakcyjną czyPrzekroczonoLimitWagowy, która korzysta z tej wcześniejszej. Dzięki temu, że metoda policzWagę jest w klasie Samochód, to można wrzucić metodę czyPrzekroczonoLimitWagowy do klasy Samochód, zamiast kopiować ją do wszystkich podklas. Natomiast klasa Samochód nie wie jak policzyć wagę każdego konkretnego rodzaju samochodu, więc deleguje tą odpowiedzialność do podklas poprzez stworzenie abstrakcyjnej metody policzWagę.

Można by to rozwiązywać na inne sposoby, np tworząc interfejs LicznikWagi i przekazywać go do metody czyPrzekroczonoLimitWagowy i wszystkich innych metod potrzebujących wagi, ale to by wymagało dużo większego nakładu pracy, a zarazem byłoby najprawdopodobniej niepotrzebne skoro mamy metody abstrakcyjne.

Jedna z ogólnych zasad przy projektowaniu nietrywialnych aplikacji to to, że klasa bazowa nie może wiedzieć o bebechach klas dziedziczących. A więc nie powinno się np robić nieabstrakcyjnej metody policzWagę w klasie Samochód, która by miała logikę dla wszystkich swoich podklas.

0

@nowacki : Klasy Abstrakcyjne
Słowem komentarza: zauważ że czasem nie obchodzi nas czy mamy do czynienia z SamochodemOsobowym czy Ciężarówką a sam fakt że mamy do czynienia z Pojazdem ;)
Z życia wzięte:

  • jeśli masz gdzieś List<String> to w większości przypadków jest ci obojętne czy to ArrayList<String> czy LinkedList<String>, interesuje cię tylko to że obie te klasy mają pewne funkcjonalności których potrzebujesz. (Obiekt klasy List<String> nie miałby oczywiście sensu bo nie miałby jak przechowywać danych :) )
0

Przykładów nigdy za dużo, dorzucę taki co akurat do mnie najbardziej przemawia, mianowicie:

Idziesz do sklepu. Prosisz o pilot do telewizora, dostajesz więc pilot do telewizora. Ale nie "pilot marki pilot", czy coś, mówiąc pilot masz abstrakcyjne pojęcie pilota: czyli, no, taki, co ma guziki 1-9, następny/poprzedni kanał, głośniej/ciszej. Nie można utworzyć właśnie takiego abstrakcyjnego pilota, bo biorąc jakiś ze sklepu dostajesz, wymyślmy, Samsunga E9283 czy Phillipsa CPP23.
Ale te w/w modele mają właśnie te cechy wspólne - owe klawisze, które należą do ogólnego, abstrakcyjnego pojęcia pilota.

0

Właśnie sprawdziłem pewną rzecz (kod w uproszczeniu).

Klasa ogólna nieabstrakcyjna Samochod i klasa dziedzicząca po niej o nazwie Osobowy;

public class Samochod
{
    void jedz()
    {
        System.out.printf("Samochod jedzie");
    }
}

public class Osobowy extended Samochod
{
    void jedz()
    {
        System.out.printf("Osobowy jedzie");
    }
}

Teraz, w funkcji main() piszę coś takiego:

Samochod pojazd;
Osobowy Ford = new Osobowy();
pojazd = Ford;

Ford.jedz();

Okazuje się, że wbrew moim przypuszczeniom wykonuje się funkcja z klasy Osobowy a nie z klasy Samochod.
Oczywiście, jeśli usunę funkcję jedz z klasy Samochod to kompilator zaprotestuje.
Myślałem jednak, że w takich sytuacjach przydaje się funkcja abstrakcyjna, żeby zmienna klasy bazowej wskazująca na klasę pochodną mogła się "orientować".
Wychodzi na to, że nie ma różnicy, czy funkcja Jedz() w klasie bazowej jest pusta, czy otagowana jako abstrakcyjna. Efekt jest dokładnie taki sam. Dlaczego tak się dzieje?

0

Jeśli znasz C++, to taka analogia może ci pomóc:

  • wszystkie metody w Javie zachowują się jak metody oznaczone 'virtual' w C++, a więc to do jakiej metody się dostaniesz nie zależy od typu referencji, a tylko typu obiektu,
  • pola w klasach nie są wirtualne i to do jakiego pola się dostaniesz zależy od typu referencji,

Jeśli nie znasz C++, to trzeba inaczej tłumaczyć:

  • przy wywołaniu metody na obiekcie sprawdzany jest dokładny typ obiektu i wywoływana jest metoda o danej sygnaturze najniżej z hierarchii,
  • przy dostępie do pola sprawdzany jest typ referencji i na podstawie niego jest wybierane odpowiednie pole - może się zdarzyć tak, że dana klasa zawiera dwa pola o tej samej nazwie, przykład: http://ideone.com/py0grV

Zamiast się dziwić, poczytaj jakąś dobrą książkę o Javie, albo chociaż: http://docs.oracle.com/javase/tutorial/

0

@Wibowit , dzięki. Bardzo przejrzyście tłumaczysz. Ja właśnie myślałem, że słowo "abstract" jest takim jakby "virtual" z C++. Stąd moja konsternacja, że to tak nie działa.
Ale dalej nie wszystko załapałem do końca odnośnie metod wirtualnych.

Co by się stało, gdyby w Twoim poprzednim przykładzie (klasa Samochód , limitWagowy(), policzWagę()) metoda policzWage() nie była abstrakcyjna tylko zadeklarowana jako zwykła, pusta metoda?

0

Akurat w tym przykładzie metoda policzWage() powinna zwrócić wagę, więc nie mogłaby być pusta. Natomiast przy metodach typu void rzeczywiście mógłbyś zastąpić metodę abstrakcyjną metodą pustą i w tym momencie nic by się nie zmieniło jeśli chodzi o obserwowalne efekty. Jednak oznaczenie metody jako abstrakcyjną powoduje, że kompilator wymusza tworzenie podklas i jest bardzo silną sugestią, że trzeba tą metodę jakoś sensownie zaimplementować.

W Javie jest więcej rzeczy, bez których teoretycznie nic by się nie popsuło, np operatory widoczności (private/ protectec/ public/ package private), ale bez tego dużo łatwiej byłoby strzelić sobie w stopę. Te rzeczy same w sobie nie są mega skomplikowane, a się przydają, więc istnieją i mają się dobrze.

1 użytkowników online, w tym zalogowanych: 0, gości: 1