Концепции ООП в 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 — это объект, одна конкретная собака
d.name = "Rex";
d.bark(); // Rex says woofDog — это класс. d — это объект. Вы можете создать сколько угодно объектов Dog; каждый получает свою собственную копию name и age, и каждый может bark() самостоятельно. Глава классы и объекты разбирает это подробно.
Четыре столпа
ООП обычно преподают вокруг четырёх идей. Каждая получает отдельную главу далее в этой части книги — то, что следует ниже, — это тридцатисекундный обзор, чтобы вы знали, куда ведёт дорога.
Инкапсуляция
Держите данные объекта приватными; вместо этого открывайте поведение. Внешний код просит account.deposit(50), а не account.balance += 50. Выгода в том, что класс контролирует собственные инварианты — никто другой не может привести его в плохое состояние.
public class Account {
private int balance; // скрыто
public void deposit(int amount) { // публичное поведение
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(); // унаследовано
c.purr(); // собственноеСм. главу наследование.
Полиморфизм
Один и тот же вызов может делать разные вещи в зависимости от фактического объекта на принимающем конце. Переменная типа Animal может указывать на Cat, Dog или Cow — вызов speak() на ней выбирает правильное поведение во время выполнения.
Animal a = new Cat();
a.speak(); // вызывает speak у Cat, хотя a типизирована как AnimalСм. главу полиморфизм.
Абстракция
Определяйте, что что-то делает, не привязываясь к тому, как. interface Shape говорит, что у каждой фигуры есть метод area(); каждая конкретная фигура — Circle, Square — предоставляет собственную формулу. Коду, работающему с фигурами, не нужно знать, какой тип у него есть.
public interface Shape {
double area();
}См. главу абстракция.
Зачем это нужно?
Для скрипта на 20 строк ООП — перебор. Отдача проявляется по мере роста программ:
- Локальное рассуждение. Класс
BankAccountвладеет своими правилами. Чтобы понять или изменить депозиты, вы читаете методdeposit— вы не прочёсываете кодовую базу в поискахbalance +=. - Повторное использование без копипасты. Наследование и композиция позволяют
SavingsAccountнадстраиваться надAccount, а не дублировать его. - Заменяемость. Функция, принимающая
Shape, работает для каждой фигуры, существующей сегодня, и каждой, которую вы добавите завтра. - Тестируемость. Небольшие объекты с чёткими обязанностями легко инстанцировать, управлять ими и проверять.
Java далеко не единственный объектно-ориентированный язык — Python, C#, Kotlin, Ruby и многие другие разделяют те же идеи с другим синтаксисом. То, что вы изучаете в этой части, переносимо.
ООП — не единственная парадигма
Даже в рамках Java вы уже писали код, который не является строго ООП: статические служебные методы, примитивная математика, простой поток управления. Современная Java смешивает парадигмы — функциональные конвейеры со стримами и лямбдами, неизменяемые данные с record-ами, декларативную работу с аннотациями. ООП — это хребет языка, а не клетка. Используйте класс, когда моделируете вещь с состоянием и поведением; тянитесь к статическому методу, когда вам нужен просто расчёт.
Проработанный пример
Полный код из приведённых выше фрагментов, собранный вместе, чтобы вы могли его запустить:
Что дальше
Теперь, когда вы знаете форму ООП, следующая глава закрепляет его основы: как определение класса становится объектом в памяти, что на самом деле делает new и чем ссылки на объекты отличаются от примитивов. Продолжайте к Классам и объектам Java.
Практика
Какое утверждение лучше всего описывает разницу между классом и объектом?