Пам’ятаю, був подібний аргумент із моїм лектором, коли я вивчав C ++ в університеті. Я просто не міг бачити сенсу використання геттерів і сеттерів, коли я міг оприлюднити змінну. Зараз я краще розумію, маючи багаторічний досвід, і я навчився кращої причини, ніж просто говорити "підтримувати інкапсуляцію".
Визначивши геттери та сетери, ви забезпечите послідовний інтерфейс, так що якщо ви хочете змінити свою реалізацію, ви менше шансів зламати залежний код. Це особливо важливо, коли ви класу експонуєте через API та використовуєте в інших додатках або сторонніми сторонами. Отже, що з речами, які потрапляють у геттер чи сетер?
Геттерам, як правило, краще реалізовуватись як просте скидання вниз для доступу до цінності, оскільки це робить їх поведінку передбачуваною. Я кажу в цілому, тому що я бачив випадки, коли геттери використовувались для доступу до значень, що маніпулюються обчисленням або навіть умовним кодом. Як правило, не так добре, якщо ви створюєте візуальні компоненти для використання під час проектування, але, здається, зручні під час виконання. Однак між цим та використанням простого методу немає різниці, за винятком того, що коли ви використовуєте метод, ви, швидше за все, більш імовірно називати метод більш доцільним, щоб функція "гетьтера" була більш очевидною при читанні коду.
Порівняйте наступне:
int aValue = MyClass.Value;
і
int aValue = MyClass.CalculateValue();
Другий варіант дає зрозуміти, що значення обчислюється, тоді як перший приклад говорить про те, що ви просто повертаєте значення, не знаючи нічого про саме значення.
Ви можете, можливо, стверджувати, що наступне буде зрозумілішим:
int aValue = MyClass.CalculatedValue;
Однак проблема полягає в тому, що ви припускаєте, що цінність вже маніпулювали в іншому місці. Так що у випадку з геттером, хоча ви можете припустити, що щось інше може відбуватися під час повернення значення, важко зробити такі речі зрозумілими в контексті властивості, а назви властивостей ніколи не повинні містити дієслів в іншому випадку важко зрозуміти з першого погляду, чи слід застосовувати ім’я, що використовується в круглих дужках під час доступу.
Однак сетери трохи інший випадок. Цілком доречно, щоб сеттер забезпечив додаткову обробку для перевірки даних, що надсилаються до властивості, викинувши виняток, якщо встановлення значення порушило б визначені межі властивості. Проблема, яка виникає у деяких розробників при додаванні обробників до сетерів, полягає в тому, що завжди існує спокуса змусити сетер зробити трохи більше, наприклад виконати обчислення або маніпулювати даними яким-небудь чином. Тут можна отримати побічні ефекти, які в деяких випадках можуть бути або непередбачуваними, або небажаними.
Що стосується сеттерів, я завжди застосовую просте правило, яке полягає в тому, щоб зробити якомога менше даних. Наприклад, я зазвичай дозволяю або граничне тестування, і округлення, щоб я міг збільшити винятки, якщо це доречно, або уникнути зайвих винятків, коли їх можна розумно уникнути. Властивості з плаваючою комою є хорошим прикладом, коли ви можете закруглювати зайві десяткові знаки, щоб уникнути підняття винятку, при цьому все ж дозволяючи вводити значення діапазону з кількома додатковими десятковими знаками.
Якщо ви застосовуєте якісь маніпуляції з введенням сеттера, у вас є та сама проблема, що і з геттером, що важко дозволити іншим знати, що робить сеттер, просто назвавши його. Наприклад:
MyClass.Value = 12345;
Чи говорить це вам щось про те, що станеться зі значенням, коли воно буде надано сетеру?
Як щодо:
MyClass.RoundValueToNearestThousand(12345);
Другий приклад розповідає вам, що саме відбудеться з вашими даними, тоді як перший не повідомить вас про те, чи буде ви будете довільно змінені. Читаючи код, другий приклад буде набагато зрозумілішим за своїм призначенням та функцією.
Я маю рацію, що це повністю переможе мету, щоб передусім отримати геттерів та сеттерів, а також слід дозволити перевірку та іншу логіку (без дивних побічних ефектів)?
Маючи геттерів та сетерів, це не про інкапсуляцію заради «чистоти», а про інкапсуляцію для того, щоб коду можна було легко відновити, не ризикуючи зміною інтерфейсу класу, який інакше порушив би сумісність класу з кодом виклику. Перевірка цілком підходить для сеттера, проте існує невеликий ризик, що зміна валідації може порушити сумісність з кодом виклику, якщо код виклику покладається на перевірку, що відбувається певним чином. Це загалом рідкісна і відносно низька небезпека, але це слід зазначити заради повноти.
Коли має відбутися перевірка?
Перевірка повинна відбуватися в контексті сеттера до фактичного встановлення значення. Це гарантує, що якщо буде викинуто виняток, стан вашого об’єкта не зміниться і потенційно може визнати недійсними його дані. Я, як правило, краще делегувати валідацію окремому методу, який би був першим, що викликається у сеттері, щоб зберегти код сеттера відносно незабрудненим.
Чи дозволено сетеру змінити значення (можливо, перетворити дійсне значення в якесь канонічне внутрішнє представлення)?
Можливо, у дуже рідкісних випадках. Взагалі, мабуть, краще цього не робити. Це така річ, яку краще залишити іншому методу.