JavaRush /Курси /JAVA 25 SELF /Створення ієрархії класів та приклади з життя

Створення ієрархії класів та приклади з життя

JAVA 25 SELF
Рівень 17 , Лекція 3
Відкрита

1. Що таке ієрархія класів?

У програмуванні (і не лише в ньому) ієрархія — це деревоподібна структура: на вершині — «загальні» сутності (базові класи), а нижче — конкретніші (підкласи). У Java ієрархію класів будують за допомогою наслідування: кожен підклас може сам бути батьком для інших підкласів і так далі.

Аналогія:

  • Тварина (Animal) — це загальний клас.
  • Ссавець (Mammal) — це частковий випадок тварини.
  • Собака (Dog) — це частковий випадок ссавця.
  • А ось конкретний «Шарік» — це вже обʼєкт класу Dog.

У коді це виглядає так:

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

Ієрархія класів дає змогу описувати спільні властивості й поведінку «нагорі», а деталі — «внизу». Це робить код логічнішим і допомагає уникнути дублювання.

Схема ієрархії

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

2. Як будувати ієрархію: логіка й практика

Визначаємо загальне і часткове

Головне правило: базовий клас має містити те, що характерно для всіх його нащадків, а все унікальне переносимо в підкласи.

Приклад:

  • Усі тварини можуть дихати та їсти — отже, методи breathe() і eat() мають бути в класі Animal.
  • Тільки птахи вміють літати — отже, метод fly() буде в класі Bird, а не в Animal.
  • Тільки собаки вміють гавкати — метод bark() буде в класі Dog.

Приклад: Тварини

// Базовий клас
class Animal {
    String name;

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

    void eat() {
        System.out.println(name + " їсть.");
    }

    void makeSound() {
        System.out.println(name + " видає звук.");
    }
}

// Підклас: Ссавець
class Mammal extends Animal {
    Mammal(String name) {
        super(name);
    }

    void feedMilk() {
        System.out.println(name + " годує дитинчат молоком.");
    }
}

// Підклас: Собака
class Dog extends Mammal {
    Dog(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " гавкає: Гав-гав!");
    }

    void wagTail() {
        System.out.println(name + " виляє хвостом.");
    }
}

// Підклас: Кішка
class Cat extends Mammal {
    Cat(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " нявкає: Няв!");
    }

    void purr() {
        System.out.println(name + " муркоче.");
    }
}

// Підклас: Птах
class Bird extends Animal {
    Bird(String name) {
        super(name);
    }

    void fly() {
        System.out.println(name + " літає.");
    }

    @Override
    void makeSound() {
        System.out.println(name + " цвірінькає: Чік-чірік!");
    }
}

Виклик у main():

public class ZooDemo {
    public static void main(String[] args) {
        Dog sharik = new Dog("Шарік");
        Cat murka = new Cat("Мурка");
        Bird sparrow = new Bird("Горобець");

        sharik.eat();        // Шарік їсть.
        sharik.makeSound();  // Шарік гавкає: Гав-гав!
        sharik.feedMilk();   // Шарік годує дитинчат молоком.
        sharik.wagTail();    // Шарік виляє хвостом.

        murka.eat();         // Мурка їсть.
        murka.makeSound();   // Мурка нявкає: Няв!
        murka.feedMilk();    // Мурка годує дитинчат молоком.
        murka.purr();        // Мурка муркоче.

        sparrow.eat();       // Горобець їсть.
        sparrow.makeSound(); // Горобець цвірінькає: Чік-чірік!
        sparrow.fly();       // Горобець літає.
    }
}

Візуалізація: дерево класів

Клас Батьківський клас Особливості
Animal
Object
name, eat(), makeSound()
Mammal
Animal
feedMilk()
Dog
Mammal
makeSound(), wagTail()
Cat
Mammal
makeSound(), purr()
Bird
Animal
fly(), makeSound()

3. Ще приклади з життя

Геометричні фігури

Ієрархії класів чудово підходять для моделювання геометрії.

// Базовий клас
class Shape {
    void draw() {
        System.out.println("Малюємо фігуру.");
    }
}

// Коло
class Circle extends Shape {
    double radius;

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

    @Override
    void draw() {
        System.out.println("Малюємо коло радіусом " + radius);
    }
}

// Прямокутник
class Rectangle extends Shape {
    double width, height;

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

    @Override
    void draw() {
        System.out.println("Малюємо прямокутник " + width + "x" + height);
    }
}

Використання:

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

        s1.draw(); // Малюємо коло радіусом 5.0
        s2.draw(); // Малюємо прямокутник 3.0x4.0
    }
}

Транспорт

class Vehicle {
    void move() {
        System.out.println("Транспорт рухається.");
    }
}

class Car extends Vehicle {
    @Override
    void move() {
        System.out.println("Автомобіль їде дорогою.");
    }
}

class Bicycle extends Vehicle {
    @Override
    void move() {
        System.out.println("Велосипедист крутить педалі.");
    }
}

main():

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

v1.move(); // Автомобіль їде дорогою.
v2.move(); // Велосипедист крутить педалі.

Користувачі

class User {
    String username;
    User(String username) { this.username = username; }
    void login() { System.out.println(username + " увійшов у систему."); }
}

class Admin extends User {
    Admin(String username) { super(username); }
    void deleteUser(String user) {
        System.out.println(username + " видалив користувача " + user);
    }
}

class Customer extends User {
    Customer(String username) { super(username); }
    void buy() { System.out.println(username + " здійснив покупку."); }
}

4. Корисні нюанси

Не зловживайте наслідуванням. Наслідування — інструмент для відношення «is-a». Якщо ви хочете сказати «Кішка — це Тварина», використовуйте наслідування. Якщо ж «Кішка містить хвіст», використовуйте композицію («has-a»).

Погано:

class Engine { /* ... */ }
class Car extends Engine { /* Автомобіль — це двигун? Ні, це композиція! */ }

Добре:

class Car {
    Engine engine; // Автомобіль містить двигун
}

Не створюйте надто глибоких ієрархій. Чим більше рівнів — тим складніше підтримувати та розуміти код. Зазвичай 2–3 рівні — це максимум для більшості завдань.

Не робіть надто «пласких» ієрархій. Якщо у вас є 20 класів і всі вони наслідують безпосередньо від одного базового, можливо, варто переглянути архітектуру.

Наслідування «заради зручності». Іноді хочеться «позичити» методи або поля, не замислюючись про логіку. Це шлях до хаосу! Якщо class A і class B не повʼязані логічно, не варто робити class B extends A лише заради кількох методів.

Порушення принципу підстановки Лісков. Якщо підклас не може бути використаний замість батьківського класу без несподіванок — ієрархію побудовано неправильно.

Дублювання коду. Якщо ловите себе на думці: «цей метод копіюється з класу в клас», можливо, час винести його в базовий клас.

5. Типові помилки під час побудови ієрархій класів

Помилка № 1: Наслідування без логіки «is-a».
Якщо ви використовуєте наслідування лише заради доступу до методів або полів, а не тому, що підклас справді «є» батьківським, ваш код швидко стане безладним. Наприклад, «Автомобіль наслідує Двигун» — це не is-a, а has-a.

Помилка № 2: Ігнорування унікальних особливостей підкласу.
Якщо всі ваші підкласи виглядають однаково й не додають нічого нового, можливо, ієрархія не потрібна — використовуйте один клас.

Помилка № 3: Дублювання коду в підкласах.
Якщо ви копіюєте одну й ту саму реалізацію в кілька підкласів, варто винести її в батьківський клас.

Помилка № 4: Надто складна або глибока ієрархія.
Багаторівневі ієрархії складно підтримувати й тестувати. Чим простіше — тим краще!

Помилка № 5: Перевизначення методів без анотації @Override.
Без анотації легко помилитися в сигнатурі методу й тоді метод не буде перевизначений, а вважатиметься новим. Завжди використовуйте @Override!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ