1. Введение

Хотим посвятить сегодняшнюю лекцию инкапсуляции. Вы уже знаете в общих чертах, что это такое.

Инкапсуляция

В чем же преимущества инкапсуляции? Их достаточно много, но я могу выделить четыре, на мой взгляд, основных:


2. Валидное внутреннее состояние

В програмах часто возникают ситуации, когда несколько классов взаимодействуют с одним и тем же объектом. В результате их совместной работы нарушается целостность данных внутри объекта – он уже не может продолжать нормально работать.

Поэтому объект должен следить за изменениями своих внутренних данных, а еще лучше — проводить их сам.

Если мы не хотим, чтобы какая-то переменная класса менялась другими классами, мы объявляем ее private, и тогда только методы её же класса смогут получить к ней доступ. Если мы хотим, чтобы значения переменных можно было только читать, но не изменять, тогда нужно добавить public getter для нужных переменных.

Например, мы хотим, чтобы все могли узнать количество элементов в нашей коллекции, но никто не мог его поменять без нашего разрешения. Тогда мы объявляем переменную private int count и метод public getCount().

Правильное использование инкапсуляции гарантирует, что ни один класс не может получить прямой доступ к внутренним данным нашего класса и, следовательно, изменить их без контроля с нашей стороны. Только через вызов методов того же класса, что и изменяемые переменные.

Лучше исходить из того, что другие программисты всегда будут использовать ваши классы самым удобным для них образом, а не самым безопасным для вас (для вашего класса). Отсюда и ошибки, и попытки заранее избавиться от них.


3. Контроль передаваемых аргументов

Иногда нужно контролировать аргументы, передаваемые в методы нашего класса. Например, наш класс описывает объект "человек" и позволяет задать дату его рождения. Мы должны проверять все передаваемые данные на их соответствие логике программы и логике нашего класса. Например, не допускать 13-й месяц, дату рождения 30 февраля и так далее.

Зачем же кому-то указывать в дате рождения 30 февраля? Во-первых, это может быть ошибка ввода данных от пользователя. Во-вторых, прежде чем программа будет работать как часы, в ней будет много ошибок. Например, возможна такая ситуация.

Программист пишет программу, которая определяет людей, у которых день рождения послезавтра. Например, сегодня 3 марта. Программа добавляет к текущему дню месяца число 2 и ищет всех, кто родился 5 марта. Вроде бы всё верно.

Вот только когда наступит 30 марта, программа не найдет никого, т.к. в календаре нет 32 марта. В программе становится гораздо меньше ошибок, когда в методы добавляют проверку переданных данных.

Помните, когда мы изучали ArrayList и разбирали его код, и там была проверка индекса в методах get и set: index больше или равен нулю и меньше длины массива. Там еще выбрасывалось исключение, если в массиве нет элемента с таким индексом. Это классический пример проверки входных данных.


4. Минимизация ошибок при изменении кода классов

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

Класс оказался настолько полезен, что вы решили его улучшить. Но если вы удалите какие-то методы этого класса, код десятков людей перестанет компилироваться. Им придется срочно все переделывать. И чем больше переделок, тем больше ошибок. Вы поломаете кучу сборок, и вас будут ненавидеть.

А когда мы меняем методы, объявленные как private, мы знаем, что нигде нет ни одного класса, который вызывал бы эти методы. Мы можем их переделать, поменять количество параметров и их типы, и зависимый код будет работать дальше. Ну или как минимум компилироваться.


5. Задаем способ взаимодействия нашего объекта со сторонними объектами

Мы можем ограничить некоторые действия, допустимые с нашим объектом. Например, мы хотим, чтобы объект можно было создать только в одном экземпляре. Даже если его создание происходит в нескольких местах проекта одновременно. И мы можем сделать это благодаря инкапсуляции.

Инкапсуляция 2

Инкапсуляция позволяет добавлять дополнительные ограничения, которые можно превратить в дополнительные преимущества. Например, класс String реализован как immutable (неизменяемый) объект. Объект класса String неизменяем с момента создания и до момента смерти. Все методы класса String (remove, substring, ...), возвращают новую строку, абсолютно не изменяя объект, у которого они были вызваны.

Инкапсуляция очень интересная штука.