Добавить в корзинуПозвонить
Найти в Дзене
Записки о Java

Лифт как учитель ООП: как три кита спасли нас от хаоса

Практическая статья для новичков на Java 11 Мы напишем консольный симулятор лифта, где каждый принцип ООП будет не «теорией из учебника», а решением реальной проблемы. Код простой, но честный — без упрощений, которые вводят в заблуждение. Представьте: вы приходите в новый офис. Видите кнопки вызова, табло с номером этажа, звук «динь». Вы не знаете, как устроен механизм — и вам не нужно знать! Вы просто нажимаете «5» → лифт едет на 5-й этаж. Это и есть суть ООП: Давайте построим эту систему шаг за шагом. Проблема без инкапсуляции:
Если сделать поля currentFloor и direction публичными, любой код сможет написать: elevator.currentFloor = 999; // Лифт на 999-м этаже? В 10-этажном здании?! Решение: спрятать состояние и дать контролируемый доступ. 💡 Почему это инкапсуляция?
Лифт сам управляет своей логикой: нельзя открыть двери на ходу, нельзя уехать на несуществующий этаж. Внешний код взаимодействует только через goToFloor() — как настоящий пользователь лифта. Проблема без наследования:
Гру
Оглавление
Рисунок: современный лифт
Рисунок: современный лифт

Практическая статья для новичков на Java 11

Мы напишем консольный симулятор лифта, где каждый принцип ООП будет не «теорией из учебника», а решением реальной проблемы. Код простой, но честный — без упрощений, которые вводят в заблуждение.

🔍 Почему лифт — идеальный пример для ООП?

Представьте: вы приходите в новый офис. Видите кнопки вызова, табло с номером этажа, звук «динь». Вы не знаете, как устроен механизм — и вам не нужно знать! Вы просто нажимаете «5» → лифт едет на 5-й этаж.

Это и есть суть ООП:

  • Инкапсуляция — вы не лезете в шахту с отвёрткой
  • Наследование — грузовой лифт похож на пассажирский, но с отличиями
  • Полиморфизм — диспетчер управляет всеми лифтами одинаково, не зная их тип

Давайте построим эту систему шаг за шагом.

🧱 Шаг 1: Инкапсуляция — «Я сам знаю, где я»

Проблема без инкапсуляции:
Если сделать поля currentFloor и direction публичными, любой код сможет написать:

elevator.currentFloor = 999; // Лифт на 999-м этаже? В 10-этажном здании?!

Решение: спрятать состояние и дать контролируемый доступ.

Рисунок: класс Elevator, часть 1
Рисунок: класс Elevator, часть 1
Рисунок: класс Elevator, часть 2
Рисунок: класс Elevator, часть 2
Рисунок: класс Elevator, часть 3
Рисунок: класс Elevator, часть 3
Рисунок: класс Elevator, часть 4
Рисунок: класс Elevator, часть 4

💡 Почему это инкапсуляция?
Лифт сам управляет своей логикой: нельзя открыть двери на ходу, нельзя уехать на несуществующий этаж. Внешний код взаимодействует только через goToFloor() — как настоящий пользователь лифта.

🧬 Шаг 2: Наследование — «Я как папа, но с особенностями»

Проблема без наследования:
Грузовой лифт почти как пассажирский, но:

  • Едет медленнее (груз тяжёлый)
  • Не открывает двери автоматически (нужна кнопка «Открыть»)
  • Имеет ограничение по весу

Если писать отдельный класс с нуля — дублирование кода. Решение: наследование.

Рисунок: класс FreightElevator, часть 1
Рисунок: класс FreightElevator, часть 1
Рисунок: класс FreightElevator, часть 2
Рисунок: класс FreightElevator, часть 2
Рисунок: класс FreightElevator, часть 3
Рисунок: класс FreightElevator, часть 3
Рисунок: класс FreightElevator, часть 4
Рисунок: класс FreightElevator, часть 4

💡 Почему это наследование?
FreightElevator получает всю базовую логику от Elevator (движение, проверка этажей), но
расширяет её: медленное движение, управление весом, отмена автоматического открытия дверей. Это не копирование — это специализация.

🎭 Шаг 3: Полиморфизм — «Диспетчер не спрашивает, кто ты»

Проблема без полиморфизма:
Диспетчер здания должен управлять разными лифтами. Без полиморфизма:

if (lift instanceof PassengerElevator) {

((PassengerElevator) lift).goToFloor(5);

} else if (lift instanceof FreightElevator) {

((FreightElevator) lift).goToFloor(5);

((FreightElevator) lift).openDoorsManually(); // Ошибка! Нет такого метода у пассажирского

}

Решение: единый интерфейс + переопределённые методы.

Рисунок: интерфейс ElevatorControl
Рисунок: интерфейс ElevatorControl

Теперь модифицируем базовый класс:

// В классе Elevator добавляем:

public class Elevator implements ElevatorControl { ... }

И грузовой лифт автоматически поддерживает интерфейс через наследование!

Диспетчер здания — полиморфный код:

Рисунок: класс BuildingDispatcher
Рисунок: класс BuildingDispatcher

Тестовый сценарий — всё вместе:

Рисунок: класс ElevatorDemo
Рисунок: класс ElevatorDemo

Что дальше?

  1. Добавьте класс ServiceElevator (служебный лифт) — он может ездить в подвал (-1, -2 этажи). Используйте наследование + переопределение валидации этажей.
  2. Реализуйте паттерн Стратегия: разные алгоритмы выбора лифта (ближайший, самый быстрый, с наименьшей загрузкой).
  3. Добавьте абстрактный класс AbstractElevator с общим кодом, но без реализации move() — пусть подклассы решают, как двигаться.

Заключение

Пример, рассмотренный в статье, можно найти по адресу:
https://github.com/ShkrylAndrei/blog_yandex/tree/main/src/main/java/info/examples/elevator