Menu
Coddy logo textTech

Klasy abstrakcyjne a interfejsy

Część sekcji Programowanie obiektowe ścieżki Java w Coddy. Lekcja 38 z 87.

Teraz, gdy rozumiesz już zarówno klasy abstrakcyjne, jak i interfejsy, pojawia się częste pytanie: kiedy używać jednego, a kiedy drugiego? Wybór zależy od tego, co próbujesz zamodelować.

Używaj klasy abstrakcyjnej, gdy klasy mają wspólną bazę ze wspólnym stanem lub zachowaniem. Klasy abstrakcyjne mogą mieć zmienne instancji, konstruktory oraz zarówno metody abstrakcyjne, jak i konkretne:

public abstract class Animal {
    protected String name;  // wspólny stan
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void sleep() {  // wspólne zachowanie
        System.out.println(name + " is sleeping");
    }
    
    public abstract void makeSound();  // musi zostać zaimplementowana
}

Użyj interfejsu, gdy chcesz zdefiniować możliwość, którą mogą współdzielić niepowiązane ze sobą klasy. Interfejsy skupiają się na tym, co obiekt potrafi robić, a nie na tym, czym jest:

public interface Flyable {
    void fly();
}

// Niespokrewnione klasy mogą współdzielić tę możliwość
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }

Oto krótkie porównanie:

CechaKlasa abstrakcyjnaInterfejs
Zmienne instancjiTakTylko stałe
KonstruktoryTakNie
Dziedziczenie wielokrotneNie (pojedyncze extends)Tak (wielokrotne implements)
Modyfikatory dostępuDowolneTylko publiczne (dla metod abstrakcyjnych)

Praktyczna wskazówka: jeśli tworzysz klasę abstrakcyjną zawierającą wyłącznie metody abstrakcyjne i nieprzechowującą żadnego stanu, interfejs będzie prawdopodobnie lepszym wyborem.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system pojazdów, który pokazuje, kiedy używać klas abstrakcyjnych, a kiedy interfejsów. Zamodelujesz pojazdy, które współdzielą stan i zachowanie dzięki klasie abstrakcyjnej, a opcjonalne możliwości dodasz za pomocą interfejsów.

Rozmieścisz swój kod w pięciu plikach:

  • Vehicle.java: Utwórz klasę abstrakcyjną, która będzie podstawą dla wszystkich pojazdów. Każdy pojazd ma pole brand (String) i pole year (int). Dodaj konstruktor inicjalizujący oba pola, metody pobierające wartość każdego z nich oraz metodę abstrakcyjną startEngine(), która zwraca String. Dodaj też konkretną metodę getInfo(), która zwraca: [brand] ([year]). To idealny przypadek użycia klasy abstrakcyjnej: pojazdy współdzielą stan (brand, year) i pewne zachowanie (getInfo), ale każdy uruchamia silnik w inny sposób.
  • Convertible.java: Zdefiniuj interfejs dla pojazdów, których dach można złożyć. Ta możliwość nie jest związana z tym, czym pojazd jest: to coś, co niektóre pojazdy potrafią robić. Zadeklaruj dwie metody: openRoof() i closeRoof(), obie zwracające String.
  • Car.java: Utwórz klasę, która rozszerza Vehicle i implementuje Convertible. Car ma dodatkowe pole numDoors (int). Użyj super, aby zainicjalizować odziedziczone pola. Zaimplementuj startEngine(), aby zwracała: [brand] car engine started. Zaimplementuj openRoof(), aby zwracała: [brand] roof opening, a closeRoof(), aby zwracała: [brand] roof closing.
  • Motorcycle.java: Utwórz klasę, która rozszerza Vehicle, ale NIE implementuje Convertible: motocykle nie mają dachów! Motorcycle ma pole hasSidecar (boolean). Zaimplementuj startEngine(), aby zwracała: [brand] motorcycle engine roaring.
  • Main.java: Połącz wszystko, aby pokazać różnicę między klasami abstrakcyjnymi a interfejsami. Otrzymasz cztery dane wejściowe: markę samochodu, rok produkcji samochodu, markę motocykla i rok produkcji motocykla.

    Utwórz Car (z 4 drzwiami) i Motorcycle (bez wózka bocznego). Najpierw pokaż wspólne zachowanie klasy abstrakcyjnej, wypisując getInfo() dla obu pojazdów. Oba dziedziczą tę metodę z Vehicle. Następnie wypisz wynik startEngine() dla każdego z nich: zwróć uwagę, że każdy typ pojazdu implementuje tę metodę inaczej.

    Na koniec pokaż możliwości interfejsu: ponieważ tylko Car implementuje Convertible, wywołaj i wypisz openRoof(), a następnie closeRoof() tylko dla samochodu.

Otrzymasz cztery dane wejściowe: markę samochodu (String), rok produkcji samochodu (int), markę motocykla (String) i rok produkcji motocykla (int).

Wynik powinien zawierać łącznie sześć wierszy. Zwróć uwagę, że oba pojazdy współdzielą stan klasy abstrakcyjnej i metodę getInfo(), ale tylko Car ma możliwość składania dachu. To pokazuje, kiedy stosować każde z tych rozwiązań!

Spróbuj swoich sił

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Wczytaj dane wejściowe
        String carBrand = scanner.nextLine();
        int carYear = scanner.nextInt();
        scanner.nextLine(); // pobierz znak nowej linii
        String motorcycleBrand = scanner.nextLine();
        int motorcycleYear = scanner.nextInt();
        
        // TODO: Utwórz Car z 4 drzwiami
        
        // TODO: Utwórz Motorcycle bez wózka bocznego (false)
        
        // TODO: Wypisz wynik getInfo() dla obu pojazdów (pokazuje wspólne działanie klasy abstrakcyjnej)
        
        // TODO: Wypisz wynik startEngine() dla obu pojazdów (pokazuje różne implementacje)
        
        // TODO: Wypisz wyniki openRoof() i closeRoof() tylko dla samochodu (pokazuje możliwości interfejsu)
    }
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Java online