Процедурное и объектно-ориентированное программирование
Выделяют две основные методики программирования: процедурное и объектно-ориентированное. Разница между ними не в синтаксисе, а в том, вокруг чего строится программа: вокруг последовательности действий или вокруг объектов, которые эти действия выполняют.
Что такое процедурное программирование
Процедурное программирование — это методика, при которой программа описывается как последовательность инструкций, выполняемых сверху вниз, а порядок выполнения меняют ветвления (if, switch) и циклы (for, while). Когда инструкций становится много, повторяющиеся куски выносят в отдельные подпрограммы — их называют процедурами, функциями или, в Java, методами.
Данные при этом живут отдельно от кода: есть переменные, и есть методы, которые эти переменные принимают и возвращают. Такой подход, дополненный правилом «только последовательность, ветвление и цикл, без goto», называют структурным программированием.
Все программы, которые мы рассматривали до этого урока, написаны именно в процедурном стиле — несмотря на то, что код физически находится внутри класса. Класс здесь работает просто как контейнер для метода main.
Проблема появляется с ростом программы: когда методов становятся десятки, а общих данных — сотни, любое изменение структуры данных заставляет править все методы, которые с ними работают. Разделения на методы уже недостаточно.
Процедурные языки программирования
Процедурный язык программирования — это язык, в котором базовой единицей построения программы является процедура (функция), а не объект. К классическим процедурным языкам относят:
- C — самый известный процедурный язык, до сих пор используется в системном программировании и ядре Linux;
- Pascal — язык, созданный для обучения структурному программированию;
- Fortran — научные и инженерные расчёты;
- COBOL — банковские и учётные системы, до сих пор в эксплуатации;
- BASIC, Ada, Go (Go поддерживает методы у типов, но не имеет классов и наследования).
Важно понимать: деление языков на «процедурные» и «объектно-ориентированные» условно. Большинство современных языков — мультипарадигменные. На C++, Python, JavaScript, PHP можно писать и процедурно, и объектно-ориентированно. Java ближе всего к чисто объектно-ориентированным языкам, но и на ней несложно писать процедурный код.
Что такое ООП
Объектно-ориентированное программирование (ООП) — методика программирования, в которой основными понятиями являются объект и класс. Прежде чем писать инструкции, в задаче выделяют объекты и описывают их с помощью классов. В классе задают характеристики объекта (поля класса) и его поведение (методы). Данные и работающий с ними код объединяются в одном месте.
ООП — одна из ключевых особенностей языка Java: в ней нельзя написать метод вне класса, а почти вся стандартная библиотека построена на классах и интерфейсах.
Полезная аналогия: процедурное программирование похоже на постройку дачного домика — нет смысла тратить время на архитектурный проект. ООП похоже на строительство многоэтажного здания, где сначала продумывают конструкцию, а уже потом заливают фундамент.
Важно
Java не является «на 100 % объектно-ориентированным» языком, как иногда пишут. Примитивные типы (int, double, boolean) объектами не являются, а static-методы вызываются без создания экземпляра. Именно поэтому весь код, написанный внутри main, остаётся процедурным.
Одна задача в двух стилях: пример на Java
Посчитаем стоимость покупки. Сначала — процедурно: данные лежат в переменных, метод получает их параметрами.
public class Main {
public static void main(String[] args) {
String name = "Coffee";
double price = 120.0;
int quantity = 3;
System.out.println(name + ": " + total(price, quantity));
}
static double total(double price, int quantity) {
return price * quantity;
}
} Теперь то же самое в объектно-ориентированном стиле: данные и поведение объединены в классе Product, а цена скрыта за модификатором private.
public class Product {
private final String name;
private final double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public double total(int quantity) {
return price * quantity;
}
public String getName() {
return name;
}
} public class Main {
public static void main(String[] args) {
Product coffee = new Product("Coffee", 120.0);
System.out.println(coffee.getName() + ": " + coffee.total(3));
}
} Результат одинаковый, но во втором варианте цена не может быть случайно испорчена снаружи, а правило расчёта стоимости лежит рядом с данными, которые оно использует. Добавление скидки или налога потребует изменить один класс, а не все места вызова.
Четыре принципа ООП
ООП в Java обычно описывают через четыре принципа: абстракцию, инкапсуляцию, наследование и полиморфизм (иногда абстракцию не выделяют отдельно, и остаются три). Подробный разбор каждого принципа с примерами кода и вопросами с собеседований — в отдельном уроке: Принципы ООП в Java.
Агрегация и композиция
Кроме наследования, классы можно связать отношением «имеет» (has-a) — агрегацией (слабая связь: объект получает готовую ссылку извне и живёт независимо) или композицией (жёсткая связь: вложенный объект создаётся владельцем и не существует без него). Разбор с примерами кода на Java — в уроке Ассоциация, агрегация и композиция.
Сравнение подходов
| Критерий | Процедурное программирование | ООП |
|---|---|---|
| Базовая единица | Процедура (метод, функция) | Класс и объект |
| Данные и поведение | Разделены: данные передаются в методы | Объединены внутри класса |
| Доступ к данным | Часто открытый, легко испортить извне | Скрыт инкапсуляцией (private + методы) |
| Повторное использование | Копирование или вызов функций | Наследование, композиция, интерфейсы |
| Рост требований | Правки расползаются по всей программе | Изменения локализованы в классе |
| Порог входа | Низкий, результат виден сразу | Выше: нужно проектирование до кода |
| Типичные языки | C, Pascal, Fortran, COBOL | Java, C#, Kotlin, Smalltalk |
| Где уместно | Скрипты, расчёты, учебные задачи | Крупные приложения с долгой поддержкой |
Когда процедурный стиль уместнее
Несмотря на явные преимущества ООП, злоупотреблять им не стоит. Если задача укладывается в десяток строк — вычислить факториал, отсортировать массив, распарсить строку, — процедурного стиля достаточно. Класс, у которого нет состояния и есть один метод, обычно не нужен: это лишний слой без пользы.
Признаки того, что пора переходить к объектам: у вас появляется несколько переменных, которые всегда передаются вместе; один и тот же набор данных обрабатывается в разных местах программы; появляются варианты поведения, различающиеся типом сущности. Здесь начинается проектирование классов.
Неочевидный момент
Наличие классов в коде ещё не делает его объектно-ориентированным. Если класс содержит только публичные поля без методов, а вся логика лежит в одном большом static-методе, это процедурная программа, записанная синтаксисом Java. Такой класс-пустышку называют анемичной моделью.
Часто задаваемые вопросы
Сколько принципов ООП — три или четыре?
Оба ответа встречаются: классическая четвёрка — абстракция, инкапсуляция, наследование, полиморфизм, но иногда абстракцию не считают отдельным принципом, и остаются три. Подробный разбор — в уроке Принципы ООП в Java.
Чем процедурное программирование отличается от функционального?
В процедурном стиле процедуры меняют состояние: переменные и поля перезаписываются по ходу выполнения. В функциональном упор на неизменяемые данные и функции без побочных эффектов, а функции можно передавать как аргументы. В Java функциональные приёмы доступны через лямбда-выражения и Stream API, но парадигма языка остаётся объектно-ориентированной.
Что выбрать — наследование или композицию?
По умолчанию композицию: она гибче и не связывает классы так жёстко, как наследование. Разбор отношений между классами с примерами кода — в уроке Ассоциация, агрегация и композиция.
Как переписать процедурный код в объектно-ориентированный?
Найдите переменные, которые всегда передаются вместе, и соберите их в класс. Перенесите туда методы, которые эти переменные принимали, и удалите из сигнатур ставшие лишними параметры. Закройте поля модификатором private и оставьте наружу только те методы, которые нужны вызывающему коду. Метод main в итоге должен превратиться в несколько строк, создающих объекты и запускающих сценарий.
Видео объяснение
Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.
Комментарии