Привіт! Сьогоднішню лекцію присвятимо інкапсуляції та почнемо її одразу з прикладів :)
Перед тобою — звичайний автомат з газованою водою. У мене до тебе є питання: як він працює? Спробуй докладно відповісти: звідки випадає склянка, як підтримується температура всередині, де зберігається лід, як автомат розуміє, який сироп додати і т.д.
Найімовірніше, відповідей на усі додаткові питання в тебе немає. Добре, можливо, не всі користуються такими автоматами, нині вони не настільки популярні.
Спробуємо навести інший приклад. Що-небудь, чим ти точно користуєшся багато разів щодня.
О, є ідея!
Розкажи, як працює пошукова система Google. Як саме вона шукає інформацію за тими словами, які ти вводиш? Чому згори видачі бачимо саме ці результати, а не інші?
Хоча ти користуєшся гуглом щодня, швидше за все, ти цього не знаєш. Але це не важливо. Адже тобі й не треба цього знати.
Ти можеш вводити запити до пошукової системи і не замислюватися, як саме вона працює. Ти можеш купити газований напій в автоматі, не знаючи як влаштовано цей автомат. Ти можеш водити авто без глибоких знань про роботу двигуна внутрішнього згоряння, і навіть без знань фізики на шкільному рівні.
Усе це можливо завдяки одному з основних принципів об'єктно-орієнтованого програмування — інкапсуляції.
Читаючи різні статті на цю тему, напевно, ти стикався з тим, що в програмуванні є два поширені поняття — інкапсуляція і приховування. І під словом «інкапсуляція» автори розуміють то одне, то інше (так уже склалося). Ми розберемо обидва терміни, щоб у тебе було повне розуміння.
Початкове значення слова «інкапсуляція» в програмуванні — об'єднання даних та методів роботи з цими даними в одній упаковці («капсулі»).
У Java у ролі упаковки-капсули виступає клас. Клас містить у собі і дані (поля класу), і методи для роботи з цими даними.
Тобі це здається очевидним, але в інших концепціях програмування все влаштовано інакше. Наприклад, у функціональному програмуванні дані чітко відокремлені від операцій над ними.
В ООП ж (об'єктно-орієнтованому програмуванні) програми складаються з класів-капсул, які є одночасно і даними, і функціями для роботи з ними.
Тепер поговоримо про приховування.
Яким же чином виходить так, що ми користуємось усілякими складними механізмами без розуміння, як вони влаштовані і на чому ґрунтується їхня робота? Все просто: їх творці надали простий та зручний інтерфейс.
На автоматі з газованою водою інтерфейс — це кнопки на панелі. Натиснувши одну кнопку, ти обираєш об'єм склянки. Натиснувши другу, обираєш сироп. Третя відповідає за додавання льоду. І це все, що потрібно зробити.
Не має значення, як саме автомат влаштовано всередині. Головне — він влаштований так, що для отримання напою користувачеві потрібно натиснути три кнопки.
Те саме і з автомобілем. Не має значення, що там відбувається у нього всередині. Головне — при натисканні правої педалі автомобіль їде вперед, а при натисканні лівої гальмує.
Саме в цьому полягає сутність приховування. Усі «начинки» програми ховаються від користувача. Для нього ця інформація є зайвою, непотрібною. Користувачеві необхідний кінцевий результат, а не внутрішній процес.
Давай для прикладу подивимося на клас
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;
}
У такій ситуації можна уважно стежити за тим, чи ніхто з твоїх колег не створює об'єкти з неправильним станом, але набагато краще було б виключити саму можливість створювати такі «неправильні об'єкти».
З приховуванням даних нам допомагають:
- модифікатори доступу (private, protected, package default);
- геттери і сеттери.
- Контроль за коректним станом об'єкту. Приклади цього були вище: завдяки сеттеру та модифікатору private, ми убезпечили нашу програму від котів з вагою 0.
- Зручність для користувача завдяки інтерфейсу. Ми залишаємо "зовні" для доступу користувача лише методи. Йому достатньо викликати їх, щоб отримати результат, і зовсім не потрібно заглиблюватися в деталі їхньої роботи.
- Зміни в коді не відображаються для користувачів. Усі зміни ми проводимо усередині методів. На користувача це не вплине: він як писав auto.gas() для газу машини, так і писатиме. А те, що ми змінили щось у роботі методу gas(), для нього залишиться непомітним: він, як і раніше, просто отримуватиме потрібний результат.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ