Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
33
Добавлен:
14.04.2015
Размер:
592.9 Кб
Скачать

1. Введення

Оголошення класів є центральною темою курсу, оскільки будь-яка програма на

Java - це набір класів. Оскільки типи є ключовою конструкцією мови, їх

структура досить складна, має багато тонкощів. Тому ця тема розділена на дві

голови.

Ця глава починається з продовження теми минулого глави - ​​імена та доступ до іменованих

елементам мови. Необхідно розглянути механізм розмежування доступу в Java, як

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

класів.

Наступна глава докладно розглядає особливості об'єктної моделі Java. Вводиться

поняття інтерфейсу. Уточнюються правила оголошення класів, і описується оголошення

інтерфейсу.

2. Модифікатори доступу

У багатьох мовах існують права доступу, які обмежують можливість

використання, наприклад, змінної в класі. Наприклад, легко представити два крайніх

виду прав доступа: це public, коли поле доступно з будь-якої точки програми, і private,

коли поле може бути використане тільки всередині того класу, в якому воно оголошено.

Однак перш ніж переходити до докладного розгляду цих та інших модифікаторів

доступу, необхідно уважно розібратися, навіщо вони взагалі потрібні.

2.1. Призначення модифікаторів доступу

Існує досить поширена думка, що розцінює права доступу як

якийсь елемент безпеки коду: мовляв, необхідно захищати класи від "неправильного"

використання. Наприклад, якщо в класі Human (чоловік) є поле age (вік людини),

то якийсь програміст зі злого наміру або незнання може встановити цього

полю від'ємне значення, після чого об'єкт стане працювати неправильним чином,

можуть з'явитися помилки. Для захисту такого поля age необхідно оголосити його private.

Така точка зору досить поширена, проте треба визнати, що вона далека від

істини. Основною потребою в розмежуванні прав доступу є забезпечення

невід'ємного властивості об'єктної моделі - інкапсуляції, тобто, приховування реалізації.

Виправимо приклад таким чином, щоб він коректно відображав призначення

модифікаторів доступу. Отже, нехай у класі Human є поле age цілочисельного типу,

і щоб всі бажаючі могли користуватися цим полем, воно оголошується public.

public class Human {

public int age;

}

Проходить час, і якщо в групу програмістів, які працюють над системою, входять десятки

розробників, то логічно припустити, що всі або багато з них почнуть використовувати

це поле.

Раптом може виникнути ситуація, що цілочисельного типу даних вже недостатньо, і

хотілося б змінити тип поля на дробовий. Однак якщо просто змінити int на double, то

незабаром все розробники, які користувалися класом Human і його полем age, виявлять,

що в їх коді з'явилися помилки, тому що поле раптом стало дробовим, і в рядках,

подібної цієї:

Human h = getHuman ();

int i = h.age; / / Помилка!

буде виникати помилка через спробу провести неявним чином звуження примітивного

типу.

Виходить, що подібна зміна (загалом, невелике і локальне) зажадає

модифікації багатьох і багатьох класів. Тому внесення його виявиться неприпустимим,

невиправданим з точки зору кількості зусиль, які необхідно затратити. Те

Тобто, оголосивши один раз поле або метод як public, можна опинитися в ситуації, коли

найменші зміни (імені, типу, характеристик, правил використання) в подальшому

стануть неможливі.

Навпаки, якби поле було оголошено як private, а для читання і зміни його значення

були б введені додаткові методи, то ситуація змінилася б в корені:

public class Human {

private int age;

/ / Метод, який повертає значення age

public int getAge () {

return age;

}

/ / Метод, який встановлює значення age

public void setAge (int a) {

age = a;

}

}

В цьому випадку з цим класом могло б працювати безліч програмістів, і могло б

бути створена велика кількість класів, які використовують тип Human, але модифікатор

private дає гарантію, що ніхто безпосередньо цим полем не користується, і зміна його

типу було б абсолютно безболісною операцією, пов'язаної зі зміною в рівно

одному класі.

Отримання величини віку виглядало б так:

Human h = getHuman ();

int i = h.getAge (); / / Звернення через метод

Розглянемо, як виглядає процес зміни типу поля age:

public class Human {

/ / Поле отримує новий тип double

private / * int * / double age;

/ / Старі методи працюють з округленням значення

public int getAge () {

return (int) Math.round (age);

}

public void setAge (int a) {

age = a;

}

/ / Додаються нові методи для роботи з типом double

public double getExactAge () {

return age;

}

Призначення модифікаторів доступу

public void setExactAge (double a) {

age = a;

}

}

Видно, що старі методи, які можливо вже застосовуються в безлічі місць,

залишилися без зміни. Точніше, залишився без змін їх зовнішній формат, а внутрішня

реалізація ускладнилася. Але така зміна не потребуватиме ніяких модифікацій інших

класів системи. Приклад використання

Human h = getHuman ();

int i = h.getAge (); / / Коректно

залишається вірним, мінлива i отримує коректне ціле значення. Проте зміни

вводилися для можливості працювати з дробовими величинами. Для цього були додані

нові методи, і у всіх місцях, де потрібне точне значення віку, необхідно

звертатися до них:

Human h = getHuman ();

double d = h.getExactAge (); / / Точне значення віку

Отже, в клас була додана нова можливість, не вимагала ніяких змін

вже написаного коду.

За рахунок чого була досягнута така гнучкість? Необхідно виділити властивості об'єкта,

які необхідні майбутнім користувачам цього класу, і їх зробити доступними (в

даному випадку, public). Ті ж елементи класу, що містять деталі внутрішньої реалізації

логіки класу, бажано приховувати від зовнішнього світу, щоб не утворилися

небажані залежності, які можуть серйозно стримати розвиток системи.

Цей приклад одночасно ілюструє і інше теоретичне правило написання

об'єктів, а саме: у більшості випадків доступ до полів краще реалізовувати через

спеціальні методи (accessors) для читання (getters) і запису (setters). Тобто, саме поле

розглядається як деталь внутрішньої реалізації. Дійсно, якщо розглядати

зовнішній інтерфейс об'єкта як цілком складається з допустимих дій, то

доступними елементами повинні бути тільки методи, які реалізують ці дії. Один

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

Є й інші міркування. Наприклад, повернемося до питання про коректне використання

об'єкту і установки вірних значень полів. Як наслідок, правильне розмежування

доступу дозволяє ввести механізми перевірки вхідних значень:

public void setAge (int a) {

(If a> = 0) {

age = a;

}

}

У цьому прикладі поле age ніколи не прийме некоректне негативне значення.

(Недоліком наведеного прикладу є те, що в разі неправильних вхідних

даних, вони просто ігнорується, немає жодних повідомлень, що дозволяють дізнатися, що

зміни поля віку насправді не відбулося; для повноцінної реалізації

методу необхідно освоїти роботу з помилками в Java).

Бувають і більш суттєві зміни логіки класу. Наприклад, дані можуть почати

зберігатися не в полях класу, а в більш надійному сховищі, наприклад, файлової системі

або базі даних. В цьому випадку методи-аксессор знову змінять свою реалізацію, і

почнуть звертатися до persistent storage (постійне сховище, наприклад, БД) для

читання / запису значень. Якщо доступу до полів класу не було, а відкритими були тільки

методи для роботи з їх значеннями, то можна досить легко змінити код цих методів,

а зовнішні типи, які використовували цей клас, абсолютно не зміняться, логіка їх

роботи залишиться тією ж.

Підіб'ємо підсумки. Функціональність класу необхідно розділяти на відкритий інтерфейс,

описує дії, які будуть використовувати зовнішні типи, і на внутрішню

реалізацію, яка використовується тільки всередині самого класу. Зовнішній інтерфейс в

Надалі модифіковані неможливо або дуже складно для великих систем, тому

його потрібно продумувати особливо ретельно. Деталі внутрішньої реалізації можуть

бути змінені на будь-якому етапі, якщо вони не змінюють логіку роботи всього класу. Завдяки

такому підходу реалізується одна з базових характеристик об'єктної моделі -

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

Таким чином, модифікатори доступу вводяться не для захисту типу від зовнішнього

користувача, а, навпаки, для захисту, або позбавлення, користувача від зайвих

залежностей від деталей внутрішньої реалізації. Що ж до неправильного

використання класу, то його творцям потрібно прагне до того, щоб клас був легкий

і зрозумілий в застосуванні, тоді таких проблем не виникне, адже програміст не стане

свідомо писати код, який породжує помилки в його програмі.

Звичайно, таке розбиття на зовнішній інтерфейс і внутрішню реалізацію не завжди

очевидно, часто умовно. Для полегшення завдання технічних дизайнерів класів в Java

введено не 2 (public і private), а 4 рівня доступу. Розглянемо їх і весь механізм

розмежування доступу в Java більш докладно.

Соседние файлы в папке Програмне_забезпечення_ОС_ИНФ_5_сем