Урок №123. Інкапсуляція, Геттери і Сеттери

  Юрій  | 

  Оновл. 1 Лют 2021  | 

 106

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

Навіщо робити змінні-члени класу закритими?

Як відповідь, скористаємося аналогією. У сучасному житті ми маємо доступ до багатьох електронних пристроїв. До телевізору є пульт дистанційного керування, за допомогою якого можна вмикати/вимикати телевізор. Керування автомобілем дозволяє в рази швидше пересуватися між двома точками. За допомогою фотоапарата можна робити знімки.

Всі ці 3 речі використовують загальний шаблон: вони надають вам простий інтерфейс (кнопка, кермо і т.д.), щоб виконати дію. Однак, те, як ці пристрої фактично працюють, приховано від вас (як від користувачів). Для натискання кнопки на пульті дистанційного керування вам не потрібно знати, що виконується під кришкою пульта для взаємодії з телевізором. Коли ви натискаєте на педаль газу в своєму автомобілі, вам не потрібно знати про те, як двигун внутрішнього згоряння змушує колеса обертатися. Коли ви робите знімок, вам не потрібно знати, як датчики збирають світло в піксельне зображення.

Такий поділ інтерфейсу і реалізації є надзвичайно корисним, оскільки дозволяє використовувати об’єкти без необхідності розуміти їх реалізацію. Це значно знижує складність використання цих пристроїв і значно збільшує їх кількість (пристрої з якими можна взаємодіяти).

З аналогічних причин поділ реалізації та інтерфейсу корисний і в програмуванні.

Інкапсуляція

В об’єктно-орієнтованому програмуванні інкапсуляція (або “приховування інформації”) — це процес прихованого зберігання деталей реалізації об’єкта. Користувачі звертаються до об’єкта через відкритий інтерфейс.

У мові C++ інкапсуляція реалізована через специфікатори доступу. Як правило, всі змінні-члени класу є закритими (приховуючи деталі реалізації), а більшість методів є відкритими (з відкритим інтерфейсом для користувача). Хоча вимога до користувачів використовувати публічний інтерфейс може здатися більш обтяжливою, ніж просто відкрити доступ до змінних-членів, але насправді це надає велику кількість корисних переваг, які покращують можливість повторного використання коду і його підтримку.

Перевага №1: Інкапсульовані класи простіші у використанні і зменшують складність ваших програм.

З повністю інкапсульованим класом вам потрібно знати тільки те, які методи є доступними для використання, які аргументи вони приймають і які значення повертають. Не потрібно знати, як клас реалізований всередині. Наприклад, клас, що містить список імен, може бути реалізований з використанням динамічного масиву, рядків C-style, std::array, std::vector, std::map, std::list або будь-якої іншої структури даних. Для використання цього класу, вам не потрібно знати деталі його реалізації. Це значно знижує складність ваших програм, а також зменшує кількість можливих помилок. Це є ключовою перевагою інкапсуляції.

Всі класи Стандартної бібліотеки C++ інкапсульовані. Уявіть, наскільки складнішим був би процес вивчення мови C++, якби вам потрібно було знати реалізацію std::string, std::vector або std::cout (і інших об’єктів) для того, щоб їх використовувати!

Перевага №2: Інкапсульовані класи допомагають захистити ваші дані і запобігають їх неправильному використанню.

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

Ці два члени пов’язані: m_length завжди повинен відповідати довжині рядка m_string. Якби m_length був відкритим, то будь-хто міг би змінити довжину рядка без зміни m_string (або навпаки). Це точно призвело б до проблем. Роблячи як m_length, так і m_string закритими, користувачі змушені використовувати методи для взаємодії з класом.

Ми також можемо самі поліпшити захист нашого класу від його неправильного використання. Наприклад, розглянемо клас з відкритою змінною-членом у вигляді масиву:

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

Однак, якщо ми зробимо масив закритим, то зможемо змусити користувача використати функцію, яка в першу чергу перевіряє коректність індексу:

Таким чином, ми захистимо цілісність нашої програми.

Примітка: Функція at() в std::array і std::vector робить щось схоже!

Перевага №3: Інкапсульовані класи легше змінити.

Розглянемо наступним простий приклад:

Хоча ця програма працює нормально, але що станеться, якщо ми вирішимо перейменувати m_number1 або змінити тип цієї змінної? Ми б зламали не тільки цю програму, але й більшу частину програм, які використовують клас Values!

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

Тепер давайте змінимо реалізацію класу:

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

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

Перевага №4: З інкапсульованими класами легше проводити відлагодження.

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

Функції доступу (геттери і сеттери)

Залежно від класу, може бути доречним (в контексті того, що робить клас) мати можливість отримувати/встановлювати значення закритих змінних-членів класу.

Функція доступу — це коротка відкрита функція, завданням якої є отримання або зміна значення закритої змінної-члена класу. Наприклад:

Тут getLength() є функцією доступу, яка просто повертає значення m_length.

Функції доступу зазвичай бувають двох типів:

   геттери — це функції, які повертають значення закритих змінних-членів класу;

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

Ось приклад класу, який використовує геттери і сеттери для всіх своїх закритих змінних-членів:

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

Правило: Надавайте функції доступу тільки в тому випадку, коли потрібно, щоб користувач мав можливість отримувати або присвоювати значення членам класу.

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

Правило: Геттери повинні використовувати тип повернення по значенню або по константному посиланню. Не використовуйте для геттерів тип повернення по неконстантному посиланню.

Висновки

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

   простіші у використанні;

   зменшують складність програми;

   захищають дані і запобігають їх неправильному використанню;

   легше змінити;

   легше відлагоджувати.

Це значно полегшує використання класів.

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (3 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *