Концепции ООП в Java
Обзор объектно-ориентированного программирования в Java: инкапсуляция, наследование, полиморфизм и абстракция.
Объектно-ориентированное программирование (ООП) — это способ организации кода вокруг объектов вашей программы, а не вокруг шагов. Вместо одного длинного скрипта, оперирующего сырыми переменными, вы описываете виды объектов в своей предметной области — User, Order, BankAccount — и наделяете каждый из них данными и поведением, которые ему нужны. Остальная часть программы обращается к этим объектам с запросами, не вмешиваясь в их внутреннее устройство.
Java создавалась именно вокруг этой идеи. Каждая строка кода на Java живёт внутри класса, а класс — это шаблон, из которого создаются объекты. До этого момента вы писали в основном процедурный код внутри main и нескольких статических вспомогательных методов; в этой части книги вы начнёте проектировать собственные классы.
Классы и объекты в одном предложении
Класс описывает вид объекта — какие данные он хранит и что умеет делать. Объект — это один конкретный экземпляр этого вида.
public class Dog {
String name;
int age;
void bark() {
System.out.println(name + " says woof");
}
}
Dog d = new Dog(); // d is an object — one specific dog
d.name = "Rex";
d.bark(); // Rex says woofDog — это класс. d — это объект. Ключевое отличие от процедурного кода: объект объединяет состояние (поля name и age (fields)) и поведение (метод bark() (method)) в единое целое. Можно создать сколько угодно объектов Dog; каждый получит свою копию name и age и сможет независимо вызывать bark(). Подробно это рассматривается в главе классы и объекты.
Четыре столпа
ООП традиционно строится на четырёх концепциях. Каждой из них посвящена отдельная глава в этой части книги — здесь лишь краткий обзор, чтобы вы понимали, куда движетесь.
Инкапсуляция
Скрывайте данные объекта; открывайте поведение. Внешний код обращается с запросом account.deposit(50), а не напрямую изменяет account.balance += 50. Преимущество в том, что класс сам управляет своими инвариантами — никто снаружи не сможет привести его в недопустимое состояние.
public class Account {
private int balance; // hidden
public void deposit(int amount) { // public behavior
if (amount <= 0) throw new IllegalArgumentException();
balance += amount;
}
}Подробнее — в главе инкапсуляция.
Наследование
Класс может расширять другой, перенимая его поля и методы и добавляя к ним свои. Cat extends Animal повторно использует всё, что уже есть в Animal, и определяет лишь то, что специфично для кошек.
public class Animal {
void breathe() { System.out.println("inhale, exhale"); }
}
public class Cat extends Animal {
void purr() { System.out.println("rrr"); }
}
Cat c = new Cat();
c.breathe(); // inherited
c.purr(); // ownПодробнее — в главе наследование.
Полиморфизм
Один и тот же вызов может вести себя по-разному в зависимости от того, какой объект его получает. Переменная типа Animal может указывать на Cat, Dog или Cow — вызов speak() на ней выбирает нужное поведение во время выполнения.
Animal a = new Cat();
a.speak(); // calls Cat's speak, even though a is typed as AnimalПодробнее — в главе полиморфизм.
Абстракция
Определяйте что делает объект, не фиксируя как. interface Shape говорит, что у каждой фигуры есть метод area(); каждая конкретная фигура — Circle, Square — предоставляет собственную формулу. Код, работающий с фигурами, не обязан знать, с каким именно их видом он имеет дело.
public interface Shape {
double area();
}Подробнее — в главе абстракция.
Четыре столпа в сводной таблице
| Столп | Идея в одной строке | Инструмент Java |
|---|---|---|
| Инкапсуляция | Скрыть данные, открыть поведение | поля private + публичные методы |
| Наследование | Повторно использовать и расширять класс | extends |
| Полиморфизм | Один вызов, поведение выбирается во время выполнения | переопределение + переменные супертипа |
| Абстракция | Определить что, а не как | интерфейсы и абстрактные классы |
Столпы намеренно пересекаются: полиморфизм опирается на наследование, абстракция обеспечивается через инкапсуляцию и так далее. Воспринимайте их не как четыре отдельные возможности, которые нужно заучить, а как четыре угла зрения на одну идею — моделировать программу как взаимодействующие объекты.
Зачем это нужно?
Для скрипта в 20 строк ООП избыточно. Его преимущества проявляются по мере роста программы:
- Локальное рассуждение. Класс
BankAccountвладеет своими правилами. Чтобы понять или изменить логику пополнения, достаточно прочитать методdeposit— нет нужды искать по всей кодовой базе вхожденияbalance +=. - Повторное использование без копирования. Наследование и композиция позволяют
SavingsAccountстроиться на основеAccount, а не дублировать его. - Взаимозаменяемость. Функция, принимающая
Shape, работает с любой фигурой, которая существует сегодня, и с любой, которую вы добавите завтра. - Тестируемость. Небольшие объекты с чёткими обязанностями легко создавать, использовать и проверять.
Java — далеко не единственный объектно-ориентированный язык: Python, C#, Kotlin, Ruby и многие другие разделяют те же идеи с разным синтаксисом. Всё, что вы изучите в этой части, применимо и там.
ООП — не единственная парадигма
Даже в Java вы уже писали код, который не является строго объектно-ориентированным: статические вспомогательные методы, примитивные вычисления, обычный управляющий поток. Современная Java совмещает парадигмы — функциональные конвейеры со стримами и лямбдами, неизменяемые данные с записями, декларативную работу с аннотациями. ООП — это хребет языка, а не клетка. Используйте класс, когда моделируете сущность с состоянием и поведением; прибегайте к статическому методу, когда просто нужно вычисление.
Пример с работающим кодом
Полный код из приведённых выше фрагментов, собранный вместе, чтобы вы могли его запустить:
Что дальше
Теперь, когда вы знаете общую картину ООП, следующая глава разбирает его основы: как из определения класса получается объект в памяти, что именно делает new и чем ссылки на объекты отличаются от примитивов. Переходите к главе классы и объекты в Java.