Якщо змінна має getter та setter, чи повинна вона бути загальнодоступною?


23

У мене є клас зі змінною, яка є приватною, а в класі є геттер і сетер для цієї змінної. Чому б не оприлюднити цю змінну?

Єдиний випадок, на який я думаю, що вам доведеться використовувати getters і setters, якщо вам потрібно виконати якусь операцію, крім набору або get. Приклад:

void my_class::set_variable(int x){
   /* Some operation like updating a log */
   this->variable = x;
}

10
Геттери та сетери існують для екранування. Колись, можливо, ви захочете це зробити this->variable = x + 5або зателефонувати в UpdateStatisticsфункцію сетера, і в таких випадках classinstancea->variable = 5викличете проблеми.
Кодер

1
Інший приклад: set_inch, set_centimeter, get_inch, get_centimeter, з деякими страшними діями.
rwong

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

7
@Coder: Ви завжди можете додати свого геттера / сетера, коли виникне необхідність? Або це щось, що ви не можете легко зробити на C ++ (мій досвід, як правило, Delphi)?
Мар'ян Венема

Це питання: programmers.stackexchange.com/questions/21802/… може зацікавити.
Вінстон Еверт

Відповіді:


21

Ви коли-небудь чули про власність?

Властивість - це поле, у якому є "вбудовані" аксесуари (геттери та сетери). Наприклад, Java не має властивостей, але рекомендується записати геттери та сетери в приватне поле. C # має властивості.

Отже, навіщо нам потрібні геттери та сетери? В основному це нам потрібно для захисту / екранування поля. Наприклад, ви не отримуєте доступ до поля в довідці пам'яті, ви отримуєте доступ до методу, який потім змінить поле (посилання). Цей метод здатний виконувати деякі операції, які користувач не бажає знати ( інкапсуляція поведінки ), як у вашому прикладі. Уявімо, наприклад, що десяток класів використовують ваше загальнодоступне поле і вам потрібно змінити спосіб його використання ... Вам доведеться подивитися на кожен із цих класів, щоб змінити спосіб використання поля ... Ні тому "ОЛОШ".

Наприклад, якщо у вас є булеве поле, яке називається мертвим. Вам слід подумати двічі, перш ніж оголосити setDead та isDead. Ви повинні написати аксесуари, які читаються людиною , наприклад, kill () замість setDead.

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


2
Тепер "людська читабельність" - це нарешті аргумент, який я можу "пограбувати". Всі інші аргументи, які я зазвичай чую, - це якесь майбутнє підтвердження, яке так само легко вирішується, коли виникає потреба ...
Мар'ян Венема

13
Ваша зневага до "майбутнього доказування" є загальною, але помилковою. Так, ви можете вирішити такі зміни, коли виникне потреба, якщо ви єдиний клієнт цього коду . Якщо ви ніколи не публікуєте рішення для зовнішніх клієнтів, ви можете просто взяти на себе технічну заборгованість, і нікому ніколи цього не потрібно знати. Але внутрішні проекти мають звичку ставати загальнодоступними, коли ви найменше цього очікуєте, і тоді вам буде горе.
Кіліан Фот

2
Ви насправді піднімаєте фантастичну точку. убивати - це не набір / отримання, це дія. Це повністю приховує реалізацію - це те, як ви кодуєте OO. Властивості, з іншого боку, такі ж дурні, як і сетери / геттери - дурніші навіть тому, що простий синтаксис спонукає людей просто додавати їх до змінних, коли вони навіть не потрібні (OO-Furbar очікує, що це станеться). Хто б подумав використати kill (), коли вони могли просто додати тег "властивості" до свого мертвого прапора?
Білл К

1
Питання позначено тегом C ++. C ++ не підтримує властивості, то чому б ви навіть це публікували?
Томас Едінг

1
@ThomasEding: C ++ дещо унікальний тим, що має оператори, що перезаписують можливість присвоєння. Хоча C ++ не використовує термін "властивість" так само, як мови, як C # або VB.NET, концепція все ще існує. У C ++ можна використовувати перевантаження оператора, щоб foo.bar = biz.baz+5викликати функцію bizдля оцінки baz, а потім додати до неї п'ять і викликати функцію, fooщоб встановити barотримане значення. Такі перевантаження не називаються "властивостями", але вони можуть служити одній і тій же цілі.
supercat

25

Це не найпопулярніша думка, але я не бачу великої різниці.

Сетери та геттери - досить погана ідея. Я подумав про це, і чесно кажучи, я не можу придумати різницю між сеттером / getter майном та публічною змінною на практиці.

У ТЕОРІЇ сеттер і гетьтер або властивість додають місце для здійснення додаткових дій, коли змінна встановлюється / отримується і теоретично вони ізолюють ваш код від змін.

Насправді я рідко бачу сеттерів та одержувачів, які використовуються для додавання дії, і коли ви хочете додати дію, ви хочете додати її до ВСІХ сеттерів або одержувачів класу (наприклад, ведення журналів), які повинні змусити вас думати, що потрібно бути кращим рішенням.

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

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

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

До речі, я жодним чином не виступаю за публічні змінні - я кажу, що setters та getters (і навіть властивості) занадто близькі до того, що вже є загальнодоступними змінними.

Велика проблема полягає лише в тому, що люди, які не є OO-програмістами, занадто спокушаються використовувати сеттери і геттери, щоб перетворювати об'єкти у власні кулі (структури), які передаються навколо та оперують ними, майже навпаки, як працює об'єктно-орієнтований код.


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

1
@Kiran правда, але якщо ви встановите його в конструкторі, ви можете мати валідацію І у вас є незмінний клас. Для сетерів я не кажу, що публічна змінна, що записується, це добре, я кажу, що навіть сетери погані. Для відвідувачів я можу вважати публічну остаточну змінну життєздатною альтернативою.
Білл К

У деяких мовах, таких як C #, властивості не є бінарними сумісними із загальнодоступними змінними, тому, якщо ви зробите публічну змінну частиною свого API, але потім хочете додати певну перевірку, ви збираєтесь зламати програму свого клієнта, перетворюючи його в власність. Краще зробити його суспільним надбанням з самого початку.
Роберт Харві

@RobertHarvey Це все ще означає, що ви виявляєте властивості. Моя вся суть полягала в тому, що слід уникати опромінення властивостей, а при необхідності слід намагатися обмежити їх дітьми та зробити свій клас непорушним. Якщо ваш клас незмінний, навіщо вам потрібен код в геттері - і, отже, випливає, що ви також можете не мати Getter. Якщо ви не в змозі писати клас без відкритих змінних властивостей, то так, сеттер завжди кращий за публічну змінну, що записується (а постріл у ногу - це завжди краще, ніж удар у кишечник).
Білл К

1
Стан, що змінюється, є прийнятним, але знову ж таки, ви повинні попросити ваш об’єкт зробити щось для вас, а не робити щось для вашого об'єкта - тому, коли ви мутуєте свій стан, сеттер - це не чудовий спосіб зробити це, спробуйте написати бізнес-метод з логікою замість цього. Наприклад, "перемістити (Up5), а не встановитиZLocation (getZLocation () + 5)".
Білл К

8

Користувач геттерів та сеттерів переходить до принципу інкапсуляції . Це дозволить вам змінити, як все працює в класі, і підтримувати все, що функціонує.

Наприклад, якщо 3 інші об'єкти дзвонять, foo.barщоб отримати значення бар, і ви вирішили змінити назву бар на далеко, у вас є проблеми на руках. Якщо об'єкти, що викликаються, foo.barвам доведеться змінити всі класи, які мають це. Якщо використовується сетер / геттер, то ви нічого не можете змінити. Іншою можливістю є зміна типу змінної, і в цьому випадку просто додайте код трансформації до геттера / сеттера, і ви все в порядку.


1
+1 - змінна-член - це реалізація, getter та setter - інтерфейс. Тільки інтерфейс повинен бути загальнодоступним. Крім того, імена getter та setter повинні мати сенс у сенсі абстракції, незалежно від того, чи лежить в основі простої змінної члена чи іншої реалізації. Там є виняток - випадки , коли підсистема побудована з щільно з'єднаних класів є найпростішим способом - але це просто виштовхує кордон даних потайний до рівня в будь-якому випадку, до класу , який представляє всю підсистему.
Steve314

Чи не можу я просто представити геттер та / або сетер, коли мені потрібно змінити роботу всередині класу? Це щось, що легко зробити в Delphi, але, можливо, не в інших мовах?
Мар'ян Венема

@ marjan Звичайно, ви можете представити геттер / сетер у будь-який момент пізніше. Тоді проблема полягає в тому, що вам потрібно повернутися назад та змінити ВСІЙ код, який використовував публічне поле замість getter / setter. Я не впевнений, чи я збентежений, чи ти. 0.o
Піт

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

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

5

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

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


Дуже дякую! Я думав про той самий випадок, який ви згадуєте. Якщо я хочу, щоб змінна була вміщена в [0,1], я повинен перевірити вхідне значення в його сетері :)
Oni

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

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

2

Ви говорите: "Єдиний випадок, на який я думаю, що вам доведеться використовувати геттери та сетери, - це якщо вам потрібно виконати якусь операцію, крім набору або отримання."

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

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


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

1

По-перше, давайте зрозуміти парадигму.

  • Структури даних -> макет пам'яті, яким можна пройти та керувати відповідними знаннями функціями.
  • Об'єкти -> автономний модуль, який приховує його реалізацію та забезпечує інтерфейс, через який можна передавати інформацію.

Де корисний геттер / сетер?

Чи корисні геттери / сетери в структурах даних? Ні.

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

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

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

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

Чи корисні геттери / сетери в об’єктах? Ні.

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

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

Справа "Край"

Існують дві ситуації, коли громадські організації, які отримують геттер / сеттерів, мають сенс

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

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

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

interface Container<T>
{
    typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
    TRef item(int index); 
}

Це порушено. Реалізації Container повинні явно передавати контроль над їх внутрішніми послугами тим, хто їх використовує. Я ще не бачив мови з змінним значенням, де це добре (мови з семантикою незмінного значення за визначенням є чіткими з точки зору корупції даних, але не обов'язково з точки зору шпигування даних).

Ви можете вдосконалити / виправити getters / setter, використовуючи лише копію семантику або за допомогою проксі:

interface Proxy<T>
{
     operator T(); //<returns a copy
     ... operator ->(); //<permits a function call to be forwarded to an element
     Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
}

interface Container<T>
{
     Proxy<T> item(int index);
     T item(int index); //<When T is a copy of the original value.
     void item(int index, T new_value); //<where new_value is used to replace the old value
}

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

  • переповнення
  • підливати
  • взаємодії з під елементом перевіряються типом / перевіряються типом (у мовах втрати типу це благо)
  • Фактичний елемент може бути, а може і не бути резидентом пам'яті.

Приватні геттери / сетери

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

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

У приватному контексті все ще можливо, щоб інші функції в сім’ї переходили до цих «геттерів і сеттерів» та маніпулювали структурою даних. Звідси я вважаю їх більш доступними та маніпуляторами. Ви можете отримати доступ до даних безпосередньо або покластись на іншого члена сім'ї, щоб отримати цю частину правильно.

Захищені геттери / сетери

У захищеному контексті він не дуже відрізняється від суспільного. Іноземні, можливо, негідні функції хочуть отримати доступ до структури даних. Тож ні, якщо вони існують, вони діють як громадські жителі / сетери.


0

З досвіду роботи на C ++ сетер / геттер корисний для 2-х сценаріїв:

  1. якщо вам доведеться виконати перевірку правильності перед присвоєнням значенню члену, наприклад, рядкам або масиву.
  2. це може бути корисно, коли потрібна перевантаження функції, як int значення присвоєння bool, коли -1 (або від'ємні значення) помилкові. і навпаки для геттера.

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

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