Привіт! Сьогоднішню лекцію присвятимо інкапсуляції та почнемо її одразу з прикладів :) Принципи інкапсуляції - 1Перед тобою — звичайний автомат з газованою водою. У мене до тебе є питання: як він працює? Спробуй докладно відповісти: звідки випадає склянка, як підтримується температура всередині, де зберігається лід, як автомат розуміє, який сироп додати і т.д. Найімовірніше, відповідей на усі додаткові питання в тебе немає. Добре, можливо, не всі користуються такими автоматами, нині вони не настільки популярні. Спробуємо навести інший приклад. Що-небудь, чим ти точно користуєшся багато разів щодня. О, є ідея! Принципи інкапсуляції - 2 Розкажи, як працює пошукова система Google. Як саме вона шукає інформацію за тими словами, які ти вводиш? Чому згори видачі бачимо саме ці результати, а не інші? Хоча ти користуєшся гуглом щодня, швидше за все, ти цього не знаєш. Але це не важливо. Адже тобі й не треба цього знати. Ти можеш вводити запити до пошукової системи і не замислюватися, як саме вона працює. Ти можеш купити газований напій в автоматі, не знаючи як влаштовано цей автомат. Ти можеш водити авто без глибоких знань про роботу двигуна внутрішнього згоряння, і навіть без знань фізики на шкільному рівні. Усе це можливо завдяки одному з основних принципів об'єктно-орієнтованого програмування — інкапсуляції. Читаючи різні статті на цю тему, напевно, ти стикався з тим, що в програмуванні є два поширені поняття — інкапсуляція і приховування. І під словом «інкапсуляція» автори розуміють то одне, то інше (так уже склалося). Ми розберемо обидва терміни, щоб у тебе було повне розуміння. Початкове значення слова «інкапсуляція» в програмуванні — об'єднання даних та методів роботи з цими даними в одній упаковці («капсулі»). У Java у ролі упаковки-капсули виступає клас. Клас містить у собі і дані (поля класу), і методи для роботи з цими даними. Принципи інкапсуляції - 3 Тобі це здається очевидним, але в інших концепціях програмування все влаштовано інакше. Наприклад, у функціональному програмуванні дані чітко відокремлені від операцій над ними. В ООП ж (об'єктно-орієнтованому програмуванні) програми складаються з класів-капсул, які є одночасно і даними, і функціями для роботи з ними. Тепер поговоримо про приховування. Яким же чином виходить так, що ми користуємось усілякими складними механізмами без розуміння, як вони влаштовані і на чому ґрунтується їхня робота? Все просто: їх творці надали простий та зручний інтерфейс. На автоматі з газованою водою інтерфейс — це кнопки на панелі. Натиснувши одну кнопку, ти обираєш об'єм склянки. Натиснувши другу, обираєш сироп. Третя відповідає за додавання льоду. І це все, що потрібно зробити. Не має значення, як саме автомат влаштовано всередині. Головне — він влаштований так, що для отримання напою користувачеві потрібно натиснути три кнопки. Те саме і з автомобілем. Не має значення, що там відбувається у нього всередині. Головне — при натисканні правої педалі автомобіль їде вперед, а при натисканні лівої гальмує. Саме в цьому полягає сутність приховування. Усі «начинки» програми ховаються від користувача. Для нього ця інформація є зайвою, непотрібною. Користувачеві необхідний кінцевий результат, а не внутрішній процес. Давай для прикладу подивимося на клас Auto:

public class Auto {

   public void gas() {

       /*усередині автомобіля відбуваються якісь складні речі
       внаслідок яких він їде вперед*/
   }

   public void brake() {

       /*усередині автомобіля відбуваються якісь складні речі
       внаслідок яких він гальмує*/
   }

   public static void main(String[] args) {

       Auto auto = new Auto();

       //Як все виглядає для користувача

       //натиснув на одну педаль - поїхав
       auto.gas();
      
       //натиснув на іншу педаль - загальмував
       auto.brake();
   }
}
Ось як виглядає приховування реалізації в Java-програмі. Все як у реальному житті: користувачеві надано інтерфейс (методи). Якщо йому потрібно, щоб автомобіль у програмі виконав дію, достатньо викликати потрібний метод. А що там відбувається всередині цих методів — інформація зайва, головне, щоб усе працювало як слід. Тут ми говорили про приховування реалізації. Крім нього в Java є ще приховування даних. Про нього ми писали в лекції про геттери і сеттери, але не буде зайвим нагадати. Наприклад, в нас є клас Cat:

public class Cat {

   public String name;
   public int age;
   public int weight;

   public Cat(String name, int age, int weight) {
       this.name = name;
       this.age = age;
       this.weight = weight;
   }

   public Cat() {
   }

   public void sayMeow() {
       System.out.println("Мяв!");
   }

  
}
Можливо, ти запам'ятав із минулої лекції, у чому проблема цього класу? Якщо ні – давай згадаємо. Проблема полягає в тому, що його дані (поля) відкриті для всіх, і інший програміст легко може створити у програмі безіменного кота з вагою 0 та віком -1000 років:

public static void main(String[] args) {

   Cat cat = new Cat();
   cat.name = "";
   cat.age = -1000;
   cat.weight = 0;

}
У такій ситуації можна уважно стежити за тим, чи ніхто з твоїх колег не створює об'єкти з неправильним станом, але набагато краще було б виключити саму можливість створювати такі «неправильні об'єкти». Принципи інкапсуляції - 4 З приховуванням даних нам допомагають:
  1. модифікатори доступу (private, protected, package default);
  2. геттери і сеттери.
Туди можемо, наприклад, закласти перевірку, чи ніхто не намагається привласнити коту негативне число як вік. Як ми говорили раніше, автори різних статей про інкапсуляцію мають на увазі або інкапсуляцію (об'єднання даних і методів), або приховування, або те й інше. У Java присутні обидва механізми (в інших ООП-мовах це не обов'язково так), тому останній варіант буде найбільш правильним. Використання інкапсуляції дає нам кілька важливих переваг:
  1. Контроль за коректним станом об'єкту. Приклади цього були вище: завдяки сеттеру та модифікатору private, ми убезпечили нашу програму від котів з вагою 0.

  2. Зручність для користувача завдяки інтерфейсу. Ми залишаємо "зовні" для доступу користувача лише методи. Йому достатньо викликати їх, щоб отримати результат, і зовсім не потрібно заглиблюватися в деталі їхньої роботи.

  3. Зміни в коді не відображаються для користувачів. Усі зміни ми проводимо усередині методів. На користувача це не вплине: він як писав auto.gas() для газу машини, так і писатиме. А те, що ми змінили щось у роботі методу gas(), для нього залишиться непомітним: він, як і раніше, просто отримуватиме потрібний результат.