Що слід дозволити всередині геттерів та сетерів?


45

Я потрапив у цікавий інтернет-аргумент про геттер та сетер методи та інкапсуляцію. Хтось сказав, що все, що вони повинні зробити, - це завдання (setters) або змінний доступ (getters), щоб зберегти їх "чистими" та забезпечити інкапсуляцію.

  • Я маю рацію, що це повністю переможе мету, щоб передусім отримати геттерів та сеттерів, а також слід дозволити перевірку та іншу логіку (без дивних побічних ефектів)?
  • Коли має відбутися перевірка?
    • При встановленні значення всередині сеттера (щоб захистити об'єкт від будь-якого вступу в недійсний стан - моя думка)
    • Перед встановленням значення поза сетером
    • Всередині об'єкта перед кожним вживанням значення
  • Чи дозволено сетеру змінити значення (можливо, перетворити дійсне значення в якесь канонічне внутрішнє представлення)?

18
Найкраще в getters і setters - це можливість їх не мати , тобто залишити сетер, і у вас є властивість лише для читання, відпустка getter і у вас є параметр config, поточне значення якого ніхто не займає.
Майкл Боргвардт

7
@MichaelBorgwardt Залиште обидва, щоб мати чистий інтерфейс «розповісти, не питати». Властивості = потенційний запах коду.
Конрад Рудольф


2
Сетерів також можна використовувати для піднесення події .
Джон Ісая Кармона

@KonradRudolph Я погоджуюся з вашим твердженням, хоча я хотів би дуже підкреслити слово "потенціал".
Філ

Відповіді:


37

Пам’ятаю, був подібний аргумент із моїм лектором, коли я вивчав C ++ в університеті. Я просто не міг бачити сенсу використання геттерів і сеттерів, коли я міг оприлюднити змінну. Зараз я краще розумію, маючи багаторічний досвід, і я навчився кращої причини, ніж просто говорити "підтримувати інкапсуляцію".

Визначивши геттери та сетери, ви забезпечите послідовний інтерфейс, так що якщо ви хочете змінити свою реалізацію, ви менше шансів зламати залежний код. Це особливо важливо, коли ви класу експонуєте через API та використовуєте в інших додатках або сторонніми сторонами. Отже, що з речами, які потрапляють у геттер чи сетер?

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

Порівняйте наступне:

int aValue = MyClass.Value;

і

int aValue = MyClass.CalculateValue();

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

Ви можете, можливо, стверджувати, що наступне буде зрозумілішим:

int aValue = MyClass.CalculatedValue;

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

Однак сетери трохи інший випадок. Цілком доречно, щоб сеттер забезпечив додаткову обробку для перевірки даних, що надсилаються до властивості, викинувши виняток, якщо встановлення значення порушило б визначені межі властивості. Проблема, яка виникає у деяких розробників при додаванні обробників до сетерів, полягає в тому, що завжди існує спокуса змусити сетер зробити трохи більше, наприклад виконати обчислення або маніпулювати даними яким-небудь чином. Тут можна отримати побічні ефекти, які в деяких випадках можуть бути або непередбачуваними, або небажаними.

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

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

MyClass.Value = 12345;

Чи говорить це вам щось про те, що станеться зі значенням, коли воно буде надано сетеру?

Як щодо:

MyClass.RoundValueToNearestThousand(12345);

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

Я маю рацію, що це повністю переможе мету, щоб передусім отримати геттерів та сеттерів, а також слід дозволити перевірку та іншу логіку (без дивних побічних ефектів)?

Маючи геттерів та сетерів, це не про інкапсуляцію заради «чистоти», а про інкапсуляцію для того, щоб коду можна було легко відновити, не ризикуючи зміною інтерфейсу класу, який інакше порушив би сумісність класу з кодом виклику. Перевірка цілком підходить для сеттера, проте існує невеликий ризик, що зміна валідації може порушити сумісність з кодом виклику, якщо код виклику покладається на перевірку, що відбувається певним чином. Це загалом рідкісна і відносно низька небезпека, але це слід зазначити заради повноти.

Коли має відбутися перевірка?

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

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

Можливо, у дуже рідкісних випадках. Взагалі, мабуть, краще цього не робити. Це така річ, яку краще залишити іншому методу.


re: змінити значення, може бути розумним встановити його на -1 або якийсь маркер прапора NULL, якщо сеттер передасть незаконне значення
Martin Beckett

1
З цим є пара проблем. Довільно встановлення значення створює навмисний і неясний побічний ефект. Крім того, він не дозволяє коду виклику отримувати зворотній зв'язок, який міг би бути використаний для кращої боротьби з незаконними даними. Це особливо важливо при значеннях на рівні інтерфейсу користувача. Справедливою справедливістю, один виняток, про який я тільки думав, міг би бути, якщо дозволити декілька форматів дати як вхід, зберігаючи дату у стандартному форматі дати / часу. Можна дуже швидко стверджувати, що саме цей "побічний ефект" є нормалізацією даних при валідації, за умови, що вхідні дані є законними.
S.Robins

Так, одне з виправдань сеттера полягає в тому, що він повинен повертати true / false, якщо значення можна було встановити.
Мартін Бекетт

1
"Властивості з плаваючою комою є хорошим прикладом, коли ви, можливо, бажаєте округлити зайві десяткові знаки, щоб уникнути підняття винятку". За яких обставин занадто багато десяткових знаків ставить виняток?
Марк Байєрс

2
Я бачу зміну значення на канонічне подання як форму перевірки. Дефолтні пропущені значення (наприклад, поточна дата, якщо часова марка містить лише час) або масштабування значення (.93275 -> 93.28%) для забезпечення внутрішньої узгодженості повинно бути нормальним, але будь-які подібні маніпуляції повинні бути чітко закликані в документації API , особливо якщо вони є рідкісною ідіомою в API.
TMN

20

Якщо геттер / сетер просто відображає значення, то немає сенсу мати їх або робити значення приватним. Немає нічого поганого в оприлюдненні деяких змінних членів, якщо у вас є вагомі причини. Якщо ви пишете 3d точковий клас, то мати .x, .y, .z public має ідеальний сенс.

Як сказав Ральф Уолдо Емерсон, "Нерозумна послідовність - це гобгоблін маленьких розумів, який обожнюють маленькі державні діячі та філософи та дизайнери Яви".

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

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


1
Ух, дизайнери Яви божественні? </jk>

@delnan - очевидно, що ні - інакше вони будуть краще римуватися ;-)
Мартін Бекетт

Чи було б справедливим створити 3d-точку, в якій x, y, z всі Float.NAN?
Ендрю Т Фіннелл

4
Я не згоден з вашим твердженням, що "звичайне обгрунтування" (приховування внутрішньої структури) є найменш корисною властивістю геттерів та сеттерів. У C #, безумовно, корисно зробити властивість інтерфейсом, основну структуру якого можна змінити - наприклад, якщо ви хочете лише властивість, доступну для перерахування, це набагато краще сказати, IEnumerable<T>ніж примушувати її до чогось подібного List<T>. Я б сказав, що ваш приклад доступу до бази даних порушує єдину відповідальність - змішування представлення моделі з тим, як вона зберігається.
Брендон Лінтон

@AndrewFinnell - це може бути хорошим способом позначення точок як неможливого / недійсного / видаленого тощо
Martin Beckett

8

Принцип єдиного доступу Мейєра: "Усі послуги, пропоновані модулем, повинні бути доступні через єдину нотацію, яка не видає, реалізована вони через зберігання чи за допомогою обчислень." є основною причиною, що стоїть за геттерами / сеттерами, ака. Properties.

Якщо ви вирішили кешувати або ліниво обчислити одне поле класу, ви можете змінити це будь-коли, якщо у вас є лише відкриті доступності властивостей, а не конкретні дані.

Цінні об'єкти, прості структури не потребують цієї абстракції, але повноцінний клас, на мій погляд, є.


2

Загальною стратегією проектування класів, яка була введена через Ейфелеву мову, є розділення команд-запитів . Ідея полягає в тому, що метод повинен або розповісти вам щось про об'єкт, або сказати об'єкту щось зробити, але не робити і те й інше.

Це стосується лише публічного інтерфейсу класу, а не внутрішнього представлення. Розглянемо об’єкт моделі даних, який підтримується рядком у базі даних. Ви можете створити об'єкт, не завантажуючи в нього дані, тоді, коли ви вперше викликаєте геттера, він насправді робить це SELECT. Це добре, ви можете змінювати деякі внутрішні деталі про те, як представлений об'єкт, але ви не змінюєте, як він виглядає клієнтам цього об’єкта. Ви повинні мати можливість зателефонувати людям, які одержують кілька разів, і все одно отримати однакові результати, навіть якщо вони повертають різні результати.

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

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

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


1

Ще одна прийнятна річ - це клонування. У деяких випадках вам потрібно переконатися, що після того, як хтось дає ваш клас, наприклад. списку чогось, він не може змінити його у вашому класі (ні змінити об'єкт у цьому списку). Тому ви робите глибоку копію параметра в сеттері і повертаєте глибоку копію в getter. (Використання незмінних типів як параметрів - це ще одна опція, але вище припускаємо, що це неможливо) Але не клонуйте в аксесуарах, якщо це не потрібно. Легко подумати (але не належним чином) про нерухомість / геттери та сетери як постійні витрати, тому це мінна робота, яка чекає на користувача api.


1

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

Наприклад, клас перегляду може надавати centerвластивість, хоча немає ivar, який зберігає центр подання; установка centerвикликає зміни в інших Івар, як originабо transformчи будь інший , але клієнти класу не знають або піклуватися , як center зберігаються при умови , що він працює правильно. Однак, що не повинно відбуватися, це те, що налаштування centerпризводить до того, що все відбувається поза тим, що необхідно для збереження нового значення, однак це зроблено.


0

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


-4

Я схильний вважати, що сетери та геттери є злими, і їх слід використовувати лише в класах, якими керує рамка / контейнер. Правильна конструкція класу не повинна давати геттерам і сетерам.

Редагувати: добре написана стаття на цю тему .

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


1
Я думаю, ви пропускаєте пункт статті, в якому пропонується використовувати геттерів / сетерів економно та уникати опромінення даних класу, якщо це не потрібно. Цей IMHO - це розумний принцип дизайну, який повинен застосовувати розробник навмисно. Що стосується створення властивостей для класу, ви можете просто виставити змінну, але це ускладнює надання перевірки, коли змінна встановлена, або виведення значення з альтернативного джерела, коли "отримано", що суттєво порушує інкапсуляцію, і потенційно замикаючи вас у важкому для підтримки дизайні інтерфейсу.
S.Robins

@ S.Robins На мою думку, громадські жителі та сетери помиляються; публічні поля більш неправильні (якщо це порівняльно).
m3th0dman

@methodman Так, я погоджуюся, що публічне поле неправильне, проте публічна власність може бути корисною. Це властивість може використовуватися для надання місця для перевірки чи подій, пов’язаних із встановленням або поверненням даних, залежно від конкретної вимоги на той час. Самі ж геттери і сетери не помиляються. З іншого боку, як і коли вони використовуються або зловживають ними, можна вважати поганими з точки зору дизайну та ремонту, залежно від обставин. :)
S.Robins

@ S.Robins Подумайте про основи OOP та моделювання; об'єкти повинні копіювати суб'єкти реального життя. Чи існують суб'єкти реального життя, які мають такі види операцій / властивостей, як геттери / сетери?
m3th0dman

Щоб тут не було занадто багато коментарів, я візьму цю розмову в цей чат і адресую ваш коментар там.
S.Robins
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.