W3docs

Концепции ООП в 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 woof

Dog — это класс. 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-ами, декларативную работу с аннотациями. ООП — это хребет языка, а не клетка. Используйте класс, когда моделируете вещь с состоянием и поведением; тянитесь к статическому методу, когда вам нужен просто расчёт.

Проработанный пример

Полный код из приведённых выше фрагментов, собранный вместе, чтобы вы могли его запустить:

java— editable, runs on the server

Что дальше

Теперь, когда вы знаете форму ООП, следующая глава закрепляет его основы: как определение класса становится объектом в памяти, что на самом деле делает new и чем ссылки на объекты отличаются от примитивов. Продолжайте к Классам и объектам Java.

Практика

Практика

Какое утверждение лучше всего описывает разницу между классом и объектом?