— Привіт, Аміго! Хочу присвятити сьогоднішню лекцію інкапсуляції. Ти вже маєш загальне уявлення про те, що це таке.

У чому полягають переваги інкапсуляції? Їх досить багато, але я можу виділити чотири, на мій погляд, основні:

1) Валідний внутрішній стан.

У програмах часто виникають ситуації, коли кілька класів взаємодіють з тим самим об'єктом. Внаслідок їх спільної роботи порушується цілісність даних усередині об'єкта — об'єкт не може продовжувати нормально працювати.

Тому об'єкт повинен стежити за змінами внутрішніх даних, а ще краще впроваджувати їх самостійно.

Якщо ми не хочемо, щоб якась змінна класу змінювалася іншими класами, ми оголошуємо її private, і тоді лише методи її ж класу зможуть отримати до неї доступ. Якщо хочемо, щоб значення змінних можна було лише читати, але не змінювати, тоді треба додати public getter для потрібних змінних.

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

Правильне використання інкапсуляції гарантує, що жоден клас не може отримати прямий доступ до внутрішніх даних нашого класу, а отже, змінити їх без контролю з нашого боку. Лише через виклик методів того ж класу, що і змінні, до яких можна внести зміни.

Краще виходити з того, що інші програмісти завжди будуть використовувати твої класи найзручнішим для них чином, а не найбезпечнішим для тебе (для твого класу). Звідси і помилки, і спроби заздалегідь позбутися їх.

2) Контроль аргументів, що передаються.

Іноді потрібно контролювати аргументи, що передаються до методів нашого класу. Наприклад, наш клас описує об'єкт «людина» і дозволяє задати дату його народження. Ми повинні перевіряти всі дані, що передаються, на їх відповідність логіці програми та логіці нашого класу. Наприклад, не допускати 13-й місяць, дату народження 30 лютого тощо.

— А навіщо комусь вказувати у даті народження 30 лютого?

— По-перше, це може бути помилка введення даних від користувача.

По-друге, перш ніж програма почне працювати чітко, як годинник, у ній буде багато помилок. Наприклад, можлива така ситуація.

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

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

— Пам'ятаю, коли ми вивчали ArrayList, я дивився його код, і там була перевірка індексу в методах get і set: index більший або дорівнює нулю і менше довжини масиву. Там ще кидався виняток, якщо у масиві нема елементу з таким індексом.

— Так, це класичний приклад перевірки вхідних даних.

3) Мінімізація помилок під час зміни коду класів.

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

Клас виявився настільки корисним, що ти вирішив його покращити. Але якщо ти видалиш якісь методи цього класу, код десятків людей перестане компілюватися. Їм доведеться терміново все переробляти. І чим більше переробок, тим більше помилок. Ти поламаєш купу збірок, і тебе будуть ненавидіти.

А коли ми змінюємо методи, оголошені як private, ми знаємо, що ніде нема жодного класу, який би викликав ці методи. Ми можемо їх переробити, змінити кількість параметрів та їх типи, і залежний код буде працювати далі. Ну, або, як мінімум компілюватися.

4) Задаємо спосіб взаємодії нашого об'єкта зі сторонніми об'єктами.

Ми можемо обмежити деякі дії, допустимі з нашим об'єктом. Наприклад, ми хочемо, щоб об'єкт можна було створити лише в одному екземплярі. Навіть якщо його створення відбувається у кількох місцях проєкту одночасно. І ми можемо зробити це завдяки інкапсуляції.

Інкапсуляція дозволяє додавати додаткові обмеження, які можна перетворити на додаткові переваги. Наприклад, клас String, який реалізовано як immutable (незмінний) об'єкт. Об'єкт класу String незмінний з моменту створення і до моменту смерті. Усі методи класу String (remove, substring, …), повертають новий рядок, і при цьому абсолютно не змінюють об'єкт, у якого вони викликаються.

— Оце так! Ось воно як, виявляється.

— Інкапсуляція дуже цікава штука.

— Ага.