Учасники публічних даних проти Getters, Setters


78

Зараз я працюю в Qt і тому на C ++. У мене є класи, в яких є приватні члени даних та загальнодоступні функції членів. У мене є загальнодоступні геттери та сетери для членів даних, доступних у класі.

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

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


1
Для управління модифікацією змінної одного класу в іншому класі, зробивши змінну приватною, ми можемо контролювати спосіб модифікації значення, Наприклад, якщо в іншому класі, якщо змінна змінюється на негативне за допомогою сеттера, ми можемо створити виняток з not встановлено на мінус. Роблячи це, змінну потрібно перетворити на приватну, і для доступу до цієї приватної змінної ми можемо використовувати getter.
karthik gorijavolu

Відповіді:


78

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

Приватні дані є приватними, тому ви можете замінити реалізацію, коли завгодно (і можете виконати повне відновлення, але це вже інша проблема). Після того, як ви випустите Genie з пляшки, вам буде неможливо повернути його назад.

EDIT: Після коментаря, який я зробив до іншої відповіді.

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

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

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


4
Ще одна корисна відмінність: Ви можете легко розмістити точку зупинки налагодження на функції-члена, але набагато складніше розмістити точку зупинки налагодження в місці пам'яті для всіх екземплярів класу. Це починає призводити до таких питань: Чи потрібно мені знати, коли це змінюється або доступ здійснюється? Чи існують відносини між цими членами даних, які необхідно підтримувати? Які вимоги до безпеки багатопоточності?
Джейсон Гаррісон,

37

Ні, це навіть далеко не одне і те ж.

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


1. Член публічних даних:

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


2. Метод, який повертає посилання на фрагмент даних (можливо, на приватного члена даних):

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


3. Методи отримання та / або встановлення (можливо, доступ до приватного члена даних):

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

Підхід getter / setter навіть не викриває того факту, що властивість реалізовано фізичним об'єктом. Тобто за парою геттер / сетер може не бути жодного фізичного члена даних.

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

Звичайно, існують варіанти кожного підходу. Наприклад, метод отримання, може повернути посилання const на дані, яке розмістить їх десь між (2) і (3).


1
@ AndreyT, справді хороший ... Але чи це хороша практика, коли є геттери та сеттери для приватних членів ?? Принаймні тим членам, яких можна так часто отримувати та модифікувати ...
liaK

3
Вони можуть бути не однаковими, але використовуються з тією ж метою - забезпечення загального доступу до властивості об’єкта.
Марк Ренсом,

3
Ви розглядаєте цю форму з точки зору мови С ++ - моя відповідь з точки зору дизайну додатків.

1
+1 за вказівку на те, що отримувач не повинен повертати посилання або вказівник на дані. Можливість отримати копію значно відрізняється від можливості отримати значення.
tloach

1
@liaK - Це не має нічого спільного з тим, що є доброю практикою, і все, що пов’язане з тим, що робить ваша об’єктна модель. Дивіться мою відповідь вище. Ви ставите неправильне запитання.
jmucchiello

24

Якщо у вас є геттери та сеттери для кожного з ваших елементів даних, немає сенсу робити дані приватними. Ось чому наявність геттерів і сетерів для кожного з ваших елементів даних - погана ідея. Розглянемо клас std :: string - він (мабуть) має ОДИН геттер, функцію size () і взагалі не має сетерів.

Або розглянемо BankAccountоб’єкт - чи слід нам встановлювати SetBalance()сектор, щоб змінити поточний баланс? Ні, більшість банків не подякують вам за втілення подібного. Натомість ми хочемо щось подібне ApplyTransaction( Transaction & tx ).


1
Ви хочете сказати, що нам слід уникати використання геттерів та сеттерів для членів даних?
liaK

2
+1 ви хочете мати інтерфейс за "завданням" / "операцією", а не за допомогою геттерів / установщиків чи загальнодоступних даних.
Mark B

3
Ніл, я думаю, це не гарна пропозиція. Геттери та сетери ВИМОЖНІ для системи властивостей Qt. Ви не можете мати властивості без них. Крім того, геттери та сеттери можуть виконувати інші необхідні дії, наприклад, блокувати мьютекс, запитувати базу даних, неявний обмін, підрахунок посилань, ліниве завантаження тощо.
CMircea

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

2
@Bill Ви маєте рацію - перебільшення для ефекту там. Я маю на увазі загальну поганість функцій get / set.

11

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

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

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

Майте на увазі, однак, це не означає, що кожна приватна змінна потребує власного getter / setter. У своєму банківському прикладі Ніл підказує добру думку, що іноді Геттери / Сеттери просто не мають сенсу.


11

Зробіть дані загальнодоступними. У випадку (досить малоймовірному) випадку, коли вам коли-небудь потрібна логіка в "getter" або "setter", ви можете змінити тип даних на клас проксі, який перевантажує operator=та / або operator T(де T = будь-який тип, який ви використовуєте зараз) реалізувати необхідну логіку.

Редагувати: ідея про те, що контроль доступу до даних є інкапсуляцією, в основному хибна. Інкапсуляція - це приховування деталей реалізації (загалом!), Не контроль доступу до даних.

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

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

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


2
"ти можеш змінити тип даних на проксі-клас" - так, ти можеш, але це пластир, який, швидше за все, буде більше проблем, ніж варто.
Марк Ренсом,

3
Незалежно від того, чи є геттери / сетери хорошим дизайном, "є хакерський обхідний шлях" - це не аргумент, це виправдання.
BlueRaja - Danny Pflughoeft

3
@BlueRaja: Нісенітниця. Дивіться редагування. Справа в тому, що або дані повинні бути повністю невидимими (не загальнодоступними, і жоден ґететер або сеттер), або ж вони повинні бути видимими як дані , і в цьому випадку ґеттер та сетер виявляють неправильну абстракцію.
Джеррі Коффін,

3
@Rob: Проста статистика - навіть у коді, який ведеться десятиліттями , переважна більшість геттерів / сеттерів залишаються не чим іншим, як простими передачами, які взагалі не роблять нічого корисного.
Джеррі Коффін,

4
@Jerry має рацію. Або зробіть дані загальнодоступними, або зробіть клас абстрактним і перемістіть дані до підкласу. золота середина - приватні дані з публічними збирачами та установщиками - це хак.
Джон Дайблінг

5

Якщо ви впевнені, що ваша логіка проста, і вам ніколи не потрібно робити щось інше під час читання / запису змінної, краще тримати дані загальнодоступними. У випадку C ++ я віддаю перевагу використовувати struct замість класу, щоб підкреслити той факт, що дані є загальнодоступними.

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

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


5

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


4

Причини використання загальнодоступних полів, а не гетерів та сетерів, включають:

  1. Немає незаконних цінностей.
  2. Очікується, що клієнт її відредагує.
  3. Щоб мати можливість писати такі речі, як object.XY = Z.
  4. Твердо пообіцяти, що цінність - це просто цінність, і побічних ефектів з цим не пов’язано (і не буде в майбутньому).

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

десяти питань щодо програмування на основі вартості .)


3

На суто практичній основі, я б запропонував вам почати з того, щоб зробити всі ваші учасники даних приватними, І зробити їхні геттери та сетери приватними. Дізнавшись, що насправді потрібно решті світу (тобто вашій "(l) спільноті користувачів"), ви можете виставити відповідних геттерів та / або установщиків або написати належним чином контрольовані публічні користувачі.

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


Я вважаю приватні функції get / set цілком корисними під час розробки, навіть коли вони не займаються бізнесом у відкритому інтерфейсі.
Dennis Zickefoose

2

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


2

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

Для довідки прочитайте рекомендації C ++ (C.131)


1

Я пропоную вам не мати учасників загальнодоступних даних (за винятком структур POD). Я також не рекомендую мати геттери та сеттери для всіх ваших учасників даних. Швидше, визначте чистий загальнодоступний інтерфейс для свого класу. Це може включати методи, які отримують та / або встановлюють значення властивостей, і ці властивості можуть бути реалізовані як змінні-члени. Але не робіть геттери та сетери для всіх своїх членів.

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


PODS (структури) представляють суттєвий виняток із правила "відсутність публічних даних"; шкода, що Microsoft не зробила для них винятку при розробці деяких типів значень .net. Читання / запис властивостей struct у .net мають дивні химерні поведінки, яких відкриті поля struct не мають.
supercat

1

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

Розглянемо наступне:

double premium;
double tax;

Потім ви пишете код всюди, використовуючи це premiumзначення, щоб отримати премію:

double myPremium = class.premium;

Ваші технічні характеристики щойно змінилися, і з точки зору користувача це має бути преміум premium + tax

Вам доведеться змінювати скрізь, що це premiumзначення використовується у вашому коді, і додавати taxдо нього.

Якщо замість цього ви реалізували це як таке:

double premium;
double tax;

double GetPremium(){return premium;};

Весь ваш код буде використовуватися, GetPremium()а ваші taxзміни складатимуть один рядок:

double premium;
double tax;

double GetPremium(){return premium + tax;};

1
І тоді, коли вам потрібна премія без податку, як ви називаєте нового отримувача? Натомість створіть метод для розрахунку чогось реального світу на вашому об’єкті і дозвольте йому вирішити, чи буде він поєднувати премію та податок.
jmucchiello

Так, я вважаю, що мій приклад не є справжнім геттером / сеттером. Я просто намагався проілюструвати, що геттери та сеттери дозволяють писати реалізацію незалежно від того, що відбувається за капотом. Я гадаю, кращим прикладом було б, якби змінну premium змінити на m_dPremium або щось інше.
Бен Бернетт,

0

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


0

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

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