1.2.1 Інкапсуляція
Інкапсуляція (encapsulation)– це приховання реалізації класу і відокремлення його внутрішнього представлення від зовнішнього (інтерфейсу). Використовуючи ОЗП, не прийнято використовувати прямий доступ до властивостей будь-якого з методів інших класів. Для доступу до властивостей класу прийнято використовувати спеціальні методи цього класу для одержання і зміни його властивостей.
Всередині об’єкта дані і методи можуть мати різний ступінь відкритості (або доступності). Ці ступені в мові Java детально розглядатимуться пізніше. Вони дозволяють більш тонко керувати властивістю інкапсуляції.
Відкриті члени класу утворюють зовнішній інтерфейс об’єкта. Ц та функціональність, яка доступна іншим класам. Закритими зазвичай оголошуються всі властивості класу, а також допоміжні методи, які деталізують реалізацію і від яких не повинні залежати інші частини системи.
Дякуючи прихованню реалізації, за зовнішнім інтерфейсом можна змінити внутрішню логіку окремого класу, не змінюючи код решти компонентів системи.
Забезпечення доступу до властивостей класу тільки через його методи також надає низку переваг. По-перше, набагато простіше контролювати коректні значення полів, адже пряме звернення до властивостей відслідковувати неможливо, а значить їм можуть присвоїти некоректні значення.
По-друге, без зайвих зусиль можна змінити спосіб зберігання даних. Якщо інформацію будуть зберігати не в пам’яті, а довгостроковому сховищі, таком як файлова система або база даних, то потрібно змінити лише ряд методів одного класу, а не запроваджувати цю функціональність в усі частини системи.
Накінець, програмний код написаний з використанням цього принципу легше налагоджувати. Щоб взнати, в який момент і хто змінив властивість об’єкта, який нас інтересує, достатньо додати виведення налагоджувальної інформації в той метод об’єкта, за допомогою якого здійснюється доступ до властивості цього об’єкта. Використовуючи прямий доступ до властивостей об’єкта, прийшлося б добавляти виведення налагоджувальної інформації в усі ділянки коду, де використовується об’єкт, який нас цікавить .
1.2.2. Поліморфізм
Поліморфізм – одно з основних понять в ОЗП разом з успадкуванням та інкапсуляцією. Слово грецького походження, означає“має багато форм”. Щоб зрозуміти поліморфізм стосовно ОЗП, розглянемо приклад.
Нехай ми хочемо розробити векторний графічний редактор, в якому опишемо у вигляді класів набір графічних примітивів: Point, Line, Circle, Box, і т.д.. У кожного з цих класів визначимо метод drawдля відображення відповідного примітиву на екрані.
Очевидно, що прийдеться написати код, який за необхідності відобразити рисунок буде послідовно перебирати всі примітиви, які на момент рисування є на екрані, і викликати метод drawв кожного з них. Якщо людина не знайома з поліморфізмом, то вона найімовірніше створить декілька масивів: окремий масив для кожного типу примітивів і напише код, який послідовно перебере елементи з кожного масиву і викличе для кожного елемента метод draw. Одержимо приблизно такий код:
…
// створення порожнього масиву, який може містити
// об’єктиPoint з максимальним обсягом 1000
Point [] p= new Point [1000];
Line [] l= new Line [1000];
Circle [] l= new Circle [1000];
Box [] l= new Box [1000];
…
// припустимо, що на цьому місці відбувається
// заповнення всіх масивів відповідними об’єктами
…
for (inti = 0; i<p.length; i++){ // цикл з перебором всіх комірок масиву.
// виклик метода draw() у випадку,
// якщо комірка не порожня.
if (p[i] !=null)p.draw();
}
for (int i = 0; i <l.length; i++){
if (l[i] !=null)l.draw();
}
…
for (int i = 0; i <c.length; i++){
if (c[i] !=null)c.draw();
}
for (int i = 0; i <b.length; i++){
if (b[i] !=null)b.draw();
}
…
Недолік наведеного вище коду – дублювання практично ідентичного коду для відображення кожного типу примітивів. Незручним буде й те, що при подальшій модернізації нашого графічного редактора і добавлянні можливості рисувати нові типи графічних примітивів, наприклад Text, Starі т.д. при такому підході прийдеться змінювати існуючий код и добавляти в нього визначення нових масивів, а також обробку елементів, які міститимуться в них.
Використовуючи поліморфізм, ми можемо значно спростити реалізацію подібної функціональності. Перш за все, створимо батьківський клас для всіх наших класів. Нехай ним буде клас Point. Одержимо ієрархію класів, яка зображена на рис. 1.1.
В кожного з дочірніх класів метод draw перевизначений так, щоб відображувати екземпляри кожного класу відповідним способом.
Для опису ієрархії класів, використовуючи поліморфізм, напишемо такий код:
…
Point p[] = new Point[1000];
p[0] = new Circle();
p[1] = new Point();
p[2] = new Box();
p[3] = new Line();
…
for(int i=0; i <p.length;i++){
if (p[i]!=null) p.draw();
}
…
В описаному вище прикладі масив p[] може містити будь-які об’єкти, породжені спадкоємцем класу Point. При викликові будь-якого методу будь-яким елементом цього масиву буде виконаний метод того об’єкта, який знаходиться в комірці масиву. Наприклад, якщо в комірці p[0] знаходиться об’єкт Circle, то при викликові методу draw так:
p[0].draw();
нарисується круг, а не крапка.
Наведемо формальне означення поліморфізму:
Поліморфізм () – положення теорії типів, згідно з яким імена (наприклад, змінних) можуть позначати об’єкти різних (але вони повинні мати спільного батька) класів. Значить, будь-який об’єкт, який позначається поліморфним іменем, може по-своєму реагувати на деякий набір операцій.
В процедурному програмуванні також є поняття поліморфізму, яке відрізняється від розглянутого механізму ОЗП. Процедурний поліморфізм припускає можливість створення декількох процедур або функцій з однаковим іменем, але різною кількістю або типами параметрів, які передаються. Такі од йменні функції називаються перевантаженими, а саме явище – перевантаженням (overloading). Перевантаження функцій існує й в ОЗП і називається перевантаженням методів.
Прикладом перевантаження методів у мові Java може бути клас PrintWriter, який використовується зокрема для виведення повідомлень на консоль. Цей клас має множину методів println, які відрізняються типами і/або кількістю вхідних параметрів. Ось тільки деякі з них:
voidprintln () // перехід на новий рядок
// виводить значення булевої змінної (true абоfalse)
voidprintln (booleanx)
voidprintln (Stringx) // виводить рядок – значення текстового параметра
Певні складнощі виникають при викликові перевантажених методів. В Java є спеціальні правила, які вирішують цю проблему. Вони будуть розглянуті пізніше.
