CodeGym /Kursy /JAVA 25 SELF /Tworzenie hierarchii klas, przykłady z życia

Tworzenie hierarchii klas, przykłady z życia

JAVA 25 SELF
Poziom 17 , Lekcja 3
Dostępny

1. Czym jest hierarchia klas?

W programowaniu (i nie tylko) hierarchia to struktura drzewiasta, w której na szczycie mamy „ogólne” byty (klasy bazowe), a niżej — bardziej konkretne (podklasy). W Java hierarchia klas budowana jest za pomocą dziedziczenia: każda podklasa może sama być rodzicem dla innych podklas i tak dalej.

Analogia:

  • Zwierzę (Animal) — to klasa ogólna.
  • Ssak (Mammal) — to szczególny przypadek zwierzęcia.
  • Pies (Dog) — to szczególny przypadek ssaka.
  • A konkretny „Rex” — to już obiekt klasy Dog.

W kodzie wygląda to tak:

class Animal { }
class Mammal extends Animal { }
class Dog extends Mammal { }

Hierarchia klas pozwala opisać wspólne właściwości i zachowania „na górze”, a szczegóły — „na dole”. Dzięki temu kod jest bardziej logiczny i pozbawiony duplikacji.

Schemat hierarchii

Animal
├── Mammal
│   ├── Dog
│   └── Cat
└── Bird
    └── Sparrow

2. Jak budować hierarchię: logika i praktyka

Określamy to, co wspólne i to, co szczególne

Najważniejsza zasada: klasa bazowa powinna zawierać to, co jest charakterystyczne dla wszystkich jej potomków. To, co unikalne — przenosimy do podklas.

Przykład:

  • Wszystkie zwierzęta mogą oddychać i jeść — zatem metody breathe() i eat() powinny być w klasie Animal.
  • Tylko ptaki potrafią latać — więc metoda fly() będzie w klasie Bird, a nie w Animal.
  • Tylko psy potrafią szczekać — metoda bark() będzie w klasie Dog.

Przykład: Zwierzęta

// Klasa bazowa
class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }

    void eat() {
        System.out.println(name + " je.");
    }

    void makeSound() {
        System.out.println(name + " wydaje dźwięk.");
    }
}

// Podklasa: Ssak
class Mammal extends Animal {
    Mammal(String name) {
        super(name);
    }

    void feedMilk() {
        System.out.println(name + " karmi młode mlekiem.");
    }
}

// Podklasa: Pies
class Dog extends Mammal {
    Dog(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " szczeka: Hau-hau!");
    }

    void wagTail() {
        System.out.println(name + " macha ogonem.");
    }
}

// Podklasa: Kot
class Cat extends Mammal {
    Cat(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " miauczy: Miau!");
    }

    void purr() {
        System.out.println(name + " mruczy.");
    }
}

// Podklasa: Ptak
class Bird extends Animal {
    Bird(String name) {
        super(name);
    }

    void fly() {
        System.out.println(name + " lata.");
    }

    @Override
    void makeSound() {
        System.out.println(name + " ćwierka: Ćwir-ćwir!");
    }
}

Wywołanie w main:

public class ZooDemo {
    public static void main(String[] args) {
        Dog sharik = new Dog("Rex");
        Cat murka = new Cat("Whiskers");
        Bird sparrow = new Bird("Wróbel");

        sharik.eat();        // Rex je.
        sharik.makeSound();  // Rex szczeka: Hau-hau!
        sharik.feedMilk();   // Rex karmi młode mlekiem.
        sharik.wagTail();    // Rex macha ogonem.

        murka.eat();         // Whiskers je.
        murka.makeSound();   // Whiskers miauczy: Miau!
        murka.feedMilk();    // Whiskers karmi młode mlekiem.
        murka.purr();        // Whiskers mruczy.

        sparrow.eat();       // Wróbel je.
        sparrow.makeSound(); // Wróbel ćwierka: Ćwir-ćwir!
        sparrow.fly();       // Wróbel lata.
    }
}

Wizualizacja: drzewo klas

Klasa Rodzic Cechy
Animal
Object
name, eat(), makeSound()
Mammal
Animal
feedMilk()
Dog
Mammal
makeSound(), wagTail()
Cat
Mammal
makeSound(), purr()
Bird
Animal
fly(), makeSound()

3. Więcej przykładów z życia

Figury geometryczne

Hierarchie klas świetnie nadają się do modelowania geometrii.

// Klasa bazowa
class Shape {
    void draw() {
        System.out.println("Rysujemy figurę.");
    }
}

// Okrąg
class Circle extends Shape {
    double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    void draw() {
        System.out.println("Rysujemy okrąg o promieniu " + radius);
    }
}

// Prostokąt
class Rectangle extends Shape {
    double width, height;

    Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    void draw() {
        System.out.println("Rysujemy prostokąt " + width + "x" + height);
    }
}

Użycie:

public class ShapeDemo {
    public static void main(String[] args) {
        Shape s1 = new Circle(5);
        Shape s2 = new Rectangle(3, 4);

        s1.draw(); // Rysujemy okrąg o promieniu 5.0
        s2.draw(); // Rysujemy prostokąt 3.0x4.0
    }
}

Transport

class Vehicle {
    void move() {
        System.out.println("Pojazd się porusza.");
    }
}

class Car extends Vehicle {
    @Override
    void move() {
        System.out.println("Samochód jedzie po drodze.");
    }
}

class Bicycle extends Vehicle {
    @Override
    void move() {
        System.out.println("Rowerzysta kręci pedałami.");
    }
}

main:

Vehicle v1 = new Car();
Vehicle v2 = new Bicycle();

v1.move(); // Samochód jedzie po drodze.
v2.move(); // Rowerzysta kręci pedałami.

Użytkownicy

class User {
    String username;
    User(String username) { this.username = username; }
    void login() { System.out.println(username + " zalogował się do systemu."); }
}

class Admin extends User {
    Admin(String username) { super(username); }
    void deleteUser(String user) {
        System.out.println(username + " usunął użytkownika " + user);
    }
}

class Customer extends User {
    Customer(String username) { super(username); }
    void buy() { System.out.println(username + " dokonał zakupu."); }
}

4. Przydatne wskazówki

Nie nadużywaj dziedziczenia. Dziedziczenie to narzędzie dla relacji „is-a”. Jeśli chcesz powiedzieć „Kot to Zwierzę”, użyj dziedziczenia. Jeśli natomiast „Kot zawiera Ogon”, użyj kompozycji (has-a).

Źle:

class Engine { /* ... */ }
class Car extends Engine { /* Samochód to silnik? Nie, to kompozycja! */ }

Dobrze:

class Car {
    Engine engine; // Samochód zawiera silnik
}

Nie twórz zbyt głębokich hierarchii. Im więcej poziomów — tym trudniej utrzymywać i rozumieć kod. Zwykle 2–3 poziomy to maksimum dla większości zadań.

Nie twórz też zbyt „płaskich” hierarchii. Jeśli masz 20 klas i wszystkie dziedziczą bezpośrednio po jednej bazowej, być może warto przemyśleć architekturę.

Dziedziczenie „dla wygody”. Czasem kusi, by „pożyczyć” metody lub pola bez zastanowienia nad logiką. To droga do chaosu! Jeśli class A i class B nie są logicznie powiązane, nie rób class B extends A tylko dla kilku metod.

Naruszenie zasady podstawienia Liskov. Jeśli podklasa nie może być użyta zamiast rodzica bez niespodzianek — hierarchia jest zbudowana niepoprawnie.

Duplikowanie kodu. Jeśli łapiesz się na myśli „ojej, ta metoda jest kopiowana z klasy do klasy”, być może czas wynieść ją do klasy bazowej.

5. Typowe błędy przy budowaniu hierarchii klas

Błąd nr 1: Dziedziczenie bez relacji „is-a”.
Jeśli używasz dziedziczenia tylko po to, by mieć dostęp do metod lub pól, a nie dlatego, że podklasa rzeczywiście „jest” rodzajem klasy bazowej, twój kod szybko zamieni się w chaos. Na przykład „Samochód dziedziczy po Silniku” — to nie jest is-a, tylko has-a.

Błąd nr 2: Ignorowanie unikalnych cech podklasy.
Jeśli wszystkie twoje podklasy wyglądają tak samo i nic nie dodają, być może hierarchia nie jest potrzebna — użyj jednej klasy.

Błąd nr 3: Duplikowanie kodu w podklasach.
Jeśli kopiujesz tę samą implementację do kilku podklas, warto przenieść ją do klasy rodzica.

Błąd nr 4: Zbyt skomplikowana lub zbyt głęboka hierarchia.
Wielopoziomowe hierarchie są trudne w utrzymaniu i testowaniu. Im prościej — tym lepiej!

Błąd nr 5: Nadpisywanie metod bez adnotacji @Override.
Bez tej adnotacji łatwo pomylić się w sygnaturze metody i w efekcie metoda nie zostanie nadpisana, lecz utworzona nowa. Zawsze używaj @Override!

1
Zadanie
JAVA 25 SELF, poziom 17, lekcja 3
Niedostępne
Wirtualny pupil: Bobik je 🐕
Wirtualny pupil: Bobik je 🐕
1
Zadanie
JAVA 25 SELF, poziom 17, lekcja 3
Niedostępne
Szkoła nauki jazdy: Ruch i sygnały 🚦
Szkoła nauki jazdy: Ruch i sygnały 🚦
1
Zadanie
JAVA 25 SELF, poziom 17, lekcja 3
Niedostępne
Hierarchia szkolna: Kim jestem? 🎓
Hierarchia szkolna: Kim jestem? 🎓
1
Zadanie
JAVA 25 SELF, poziom 17, lekcja 3
Niedostępne
Encyklopedia zoologiczna: Cechy kotów 🐈
Encyklopedia zoologiczna: Cechy kotów 🐈
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION