Если переменная имеет getter и setter, должна ли она быть публичной?

23

У меня есть класс с переменной, которая является частной, и у класса есть получатель и установщик для этой переменной. Почему бы не сделать эту переменную общедоступной?

Я думаю, что вам нужно использовать только геттеры и сеттеры, если вам нужно выполнить какую-либо операцию, кроме set или get. Пример:

void my_class::set_variable(int x){
   /* Some operation like updating a log */
   this->variable = x;
}
Oni
источник
10
Геттеры и сеттеры предназначены для экранирования. Когда-нибудь вы захотите это сделать this->variable = x + 5или вызвать UpdateStatisticsфункцию в установщике, и в этих случаях classinstancea->variable = 5возникнут проблемы.
Кодер
1
Другой пример: set_inch, set_centimeter, get_inch, get_centimeter, с некоторыми пугающими действиями.
rwong
Да, геттеры и сеттеры помогают вам контролировать переменные вашего экземпляра так, как вы хотите, чтобы они управлялись.
developerdoug
7
@ Кодер: вы всегда можете добавить свой геттер / сеттер, когда возникнет такая необходимость? Или это то, что вы не можете легко сделать в C ++ (мой опыт в основном Delphi)?
Марьян Венема
Этот вопрос: programmers.stackexchange.com/questions/21802/… может представлять интерес.
Уинстон Эверт

Ответы:

21

Вы когда-нибудь слышали о недвижимости?

Свойство - это поле, которое имеет «встроенные» методы доступа (методы получения и установки). Java, например, не имеет свойств, но рекомендуется записывать методы получения и установки в закрытое поле. C # имеет свойства.

Итак, зачем нам геттеры и сеттеры? В основном это нужно для защиты / защиты поля. Например, вы не обращаетесь к полю в ссылке на память, вы обращаетесь к методу, который затем изменит поле (ссылку). Этот метод может выполнять некоторые операции, которые пользователь не хочет знать ( инкапсулируя поведение ), как в вашем примере. Представьте, например, что дюжина классов использует ваше открытое поле, и вам нужно изменить способ его использования ... Вам нужно будет посмотреть на каждый из этих классов, чтобы изменить способ использования поля ... Не так что "ООлыш".

Но, например, если у вас есть логическое поле с именем dead. Вы должны дважды подумать, прежде чем объявлять setDead и isDead. Вы должны написать средства доступа, которые будут удобочитаемыми для человека , например, kill () вместо setDead.

Тем не менее, существует множество платформ, которые предполагают, что вы следуете соглашению об именах JavaBean (здесь речь идет о Java), поэтому в этих случаях вы должны объявлять все методы получения и установки после соглашения об именовании.

wleao
источник
2
Теперь «читабельный человек» - это, наконец, аргумент, который я могу «впустить». Все остальные аргументы, которые я обычно слышу, - это своего рода доказательства будущего, которые так же легко решаются, когда возникает необходимость ...
Марьян Венема
13
Ваше презрение к "проверке будущего" является распространенным, но ошибочным. Да, вы можете обратиться к таким изменениям, когда возникнет такая необходимость, если вы являетесь единственным клиентом этого кода . Если вы никогда не публикуете свое решение для внешних клиентов, вы можете просто взять на себя технические долги, и никто никогда не узнает об этом. Но внутренние проекты имеют привычку становиться публичными, когда вы меньше всего этого ожидаете, и тогда горе вам.
Килиан Фот
2
Вы на самом деле поднимаете фантастическую точку. убить не набор / получить, это действие. Он полностью скрывает реализацию - это то, как вы кодируете OO. Свойства, с другой стороны, такие же глупые, как сеттеры / геттеры - глупее даже потому, что простой синтаксис побуждает людей просто добавлять их в переменные, когда они даже не нужны (OO-Furbar ждет, когда это произойдет). Кто бы мог подумать об использовании kill (), когда они могли бы просто добавить тег "property" в свой флаг dead?
Билл К
1
Вопрос помечен вопросом C ++. C ++ не поддерживает свойства, так почему бы вам даже опубликовать это?
Томас Эдинг
1
@ThomasEding: C ++ несколько уникален тем, что имеет переопределяемые операторы присваивания. Хотя C ++ не использует термин «свойство» так же, как языки, такие как C # или VB.NET, концепция все еще существует. В C ++ возможно использовать перегрузку операторов, чтобы foo.bar = biz.baz+5вызвать функцию bizдля оценки baz, затем добавить к ней пять и вызвать функцию, fooчтобы установить barполученное значение. Такие перегрузки не называются «свойствами», но они могут служить той же цели.
суперкат
25

Это не самое популярное мнение, но я не вижу большой разницы.

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

В THEORY установщик и получатель или свойство добавляют место для выполнения некоторых дополнительных действий, когда переменная установлена ​​/ получена, и теоретически они изолируют ваш код от изменений.

В действительности я редко вижу сеттеры и геттеры, используемые для добавления действия, и когда вы действительно хотите добавить действие, вы хотите добавить его ко ВСЕМ установщикам или получателям класса (например, протоколирование), что должно заставить вас думать, что быть лучшим решением.

Что касается изоляции проектных решений, если вы измените int на long, вам все равно придется изменить сеттеры и, по крайней мере, проверить каждую строку, которая обращается к ним вручную, - не так много изоляции.

В любом случае по умолчанию следует избегать изменчивых классов, поэтому добавление сеттера должно быть последним средством. Это смягчается с помощью шаблона построителя, где значение может быть установлено до тех пор, пока объект не будет в желаемом состоянии, тогда класс может стать неизменным, и ваши установщики будут выдавать исключения.

Что касается геттеров - я до сих пор не могу придумать большую разницу между геттером и публичной конечной переменной. Проблема здесь в том, что это плохой ОО в любом случае. Вы не должны запрашивать значение у объекта и оперировать им - вы должны просить объект выполнить операцию за вас.

Кстати, я никоим образом не защищаю публичные переменные - я говорю, что сеттеры и геттеры (и даже свойства) слишком близки к тому, чтобы уже быть публичными переменными.

Большая проблема заключается в том, что люди, которые не являются программистами OO, слишком искушены использовать сеттеры и геттеры для превращения объектов в шары свойств (структуры), которые передаются и обрабатываются, в значительной степени противоположно тому, как работает объектно-ориентированный код.

Билл К
источник
Одна хорошая вещь, помимо других ответов здесь, о установщике getter: я могу добавить несколько проверок / преобразований внутри нее, потому что фактическая приватная переменная предполагает, что она имеет значение.
WinW
1
@Kiran true, но если вы установите его в конструкторе, у вас могут быть проверки и у вас есть неизменный класс. Для сеттеров я не говорю, что общедоступная переменная для записи хороша, я говорю, что даже сеттеры плохие. Для получателей я мог бы считать публичную конечную переменную жизнеспособной альтернативой.
Билл К
В некоторых языках, таких как C #, свойства не совместимы в двоичном формате с общедоступными переменными, поэтому, если вы сделаете общедоступную переменную частью своего API, но затем захотите добавить некоторую проверку, вы прервете программу своего клиента, когда преобразуете ее в недвижимость. Лучше сделать его публичной собственностью с самого начала.
Роберт Харви
@RobertHarvey Это все еще означает, что вы выставляете свойства. Весь мой смысл заключался в том, что следует избегать выставления свойств, и при необходимости вы должны пытаться ограничить их до методов получения и сделать свой класс неизменным. Если ваш класс является неизменным, зачем вам нужен код в геттере - и, следовательно, из этого следует, что у вас также может не быть геттера. Если вы неспособны написать класс без открытых изменяемых свойств, тогда да, установщик всегда лучше общедоступной переменной, доступной для записи (и получить удар в ногу всегда лучше, чем нанести удар в кишку).
Билл К
1
Изменяемое состояние приемлемо, но опять же вы должны попросить ваш объект сделать что-то для вас, а не сделать что-нибудь с вашим объектом - поэтому, когда вы изменяете свое состояние, установщик не является отличным способом сделать это, попробуйте написать бизнес-метод с логикой в этом вместо этого. Например, «двигаться (Up5) вместо setZLocation (getZLocation () + 5)».
Билл К
8

Пользователь геттеров и сеттеров переходит на принцип инкапсуляции . Это позволит вам изменить то, как все работает внутри класса, и поддерживать все в рабочем состоянии.

Например, если 3 других объекта звонят, foo.barчтобы получить значение bar, и вы решаете изменить имя bar, то у вас возникла проблема. Если вызванные объекты foo.barвам придется изменить все классы, которые имеют это. Если используется сеттер / геттер, то вам нечего менять. Другой возможностью является изменение типа переменной, в этом случае просто добавьте код преобразования в метод получения / установки, и все в порядке.


источник
1
+1 - переменная-член является реализацией, геттер и сеттер являются интерфейсом. Только интерфейс должен быть публичным. Кроме того, имена получателей и сеттеров должны иметь смысл с точки зрения абстракции, независимо от того, существует ли простая переменная-член, лежащая в их основе, или какой-либо другой реализации. Там являются исключение - случаи , когда подсистема построена из плотно соединенных классов является самым простым способом - но это просто выталкивает границу данных потайной до уровня в любом случае, к классу , который представляет всю подсистему.
Steve314
Не могу ли я просто ввести метод получения и / или установки, когда мне нужно изменить работу внутри класса? Это легко сделать в Delphi, но, возможно, не на других языках?
Марьян Венема
@ marjan Конечно, позже вы можете ввести getter / setter. Проблема в том, что вы должны вернуться и изменить ВСЕ код, который использовал открытое поле вместо getter / setter. Я не уверен, что я тот, кто смущен, или вы. 0.o
Пит
3
Лично я думаю, что это немного фиктивный аргумент. Если вы меняете значение переменной, но переменная имеет методы получения и установки, тогда не имеет значения, как она называется. Это не видимая часть интерфейса. Вы, скорее всего, захотите изменить имя получателей и самих установщиков, чтобы указать клиентам изменение или уточнение значения инкапсулированной переменной. В этом последнем случае вы не в лучшем положении, чем с публичной переменной. Это довольно далеко от аргумента, что переменная с геттерами и сеттерами более открыта, чем инкапсулирована.
CB Bailey
1
Операция по рефакторингу в любом случае практически бесплатна, поэтому я не покупаю ее. Плюс очень плохая форма использования установщика или получателя, если он вам абсолютно не нужен - в конце концов, вы получите людей, которые не понимают концепцию, просто автоматически добавляя установщики и получатели для членов, а не тогда, когда они нужны, а просто капают семена. зла по всей вашей кодовой базе.
Билл К
5

Использование методов получения и установки также позволяет вам контролировать, какой контент хранится в определенной переменной. Если контент должен иметь определенный тип или значение, часть кода установщика может быть направлена ​​на то, чтобы новое значение соответствовало этим требованиям. Если переменная общедоступна, вы не можете обеспечить выполнение этих требований.

Этот подход также делает ваш код более адаптируемым и управляемым. Гораздо проще вносить изменения в архитектуру класса, если у вас есть функции, которые скрывают эту архитектуру от всех других классов или функций, использующих этот класс. Уже упомянутое изменение имени переменной - это только одно из многих изменений, которые намного легче сделать, если у вас есть такие функции, как геттеры и сеттеры. Общая идея состоит в том, чтобы сохранить как можно больше приватности, особенно переменных вашего класса.

Кеннет
источник
Большое спасибо! Я думал о том же случае, о котором вы упомянули Если я хочу, чтобы переменная содержалась в [0,1], я должен проверить входящее значение в ее установщике :)
Они
Когда вы являетесь единственным разработчиком, работающим над проектом, легко думать, что подобные вещи бесполезны, но когда вы обнаружите, что работаете с командой, вы обнаружите, что использование таких методов очень пригодится и поможет вам избежать много ошибок в будущем.
Кеннет
Если вы используете C #, используйте свойства, вы можете использовать свойство для установки частного экземпляра, если это необходимо, основываясь на некоторой логике в установочной части свойства.
developerdoug
2

Вы говорите: «Единственный случай, я думаю, что вы должны использовать геттеры и сеттеры, это если вам нужно выполнить какую-то операцию помимо set или get».

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

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

gnasher729
источник
В последнем абзаце есть отличное замечание, которое затрагивает оба пути: требование использования методов получения и установки не позволит другому коду получить адрес чего-либо. В тех случаях, когда есть существенные преимущества и мало недостатков, когда внешний код принимает адреса вещей, такое ограничение может быть плохим. если было бы мало пользы и серьезных недостатков в разрешении такого поведения, ограничение этого могло бы быть хорошей вещью.
суперкат
1

Прежде всего, давайте проясним парадигму.

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

Где полезен геттер / сеттер?

Полезны ли геттеры / сеттеры в структурах данных? Нет .

Структура данных - это спецификация макета памяти, которая является общей для семейства функций и управляется ими.

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

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

Учитывая, что может достичь беспорядок семейства расширенных функций, есть ли способ кодировать структуру данных, чтобы мошеннические функции не все испортили? Да, они называются объектами.

Полезны ли методы получения / установки в объектах? Нет .

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

Цель / цель метода получения и установки состоит в том, чтобы позволить функциям вне объекта напрямую изменять структуру памяти объекта. Это звучит как открытая дверь, чтобы позволить жуликам ...

The Edge Case

Существуют две ситуации, в которых публичный метод получения / установки имеет смысл.

  • Часть структуры данных в объекте управляется объектом, но не контролируется объектом.
  • Интерфейс, описывающий высокоуровневую абстракцию структуры данных, где ожидается, что некоторые элементы не будут контролировать объект реализации.

Контейнеры и контейнерные интерфейсы являются прекрасными примерами обеих этих двух ситуаций. Контейнер управляет структурами данных (связанный список, карта, дерево) изнутри, но контролирует определенный элемент всем и каждому. Интерфейс абстрагирует это и полностью игнорирует реализацию и описывает только ожидания.

К сожалению, многие реализации делают это неправильно и определяют интерфейс объектов такого типа, чтобы предоставить прямой доступ к реальному объекту. Что-то типа:

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); 
}

Сломано. Реализации Контейнера должны явно передать управление своими внутренними компонентами тому, кто их использует. Я еще не видел язык изменяемых значений, где это нормально (языки с семантикой неизменяемых значений по определению хороши с точки зрения повреждения данных, но не обязательно с точки зрения шпионажа данных).

Вы можете улучшить / исправить геттеры / сеттеры, используя только семантику копирования или прокси:

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 и объединить эти действия вместе.

В частном контексте другие функции в семье все еще могут обходить эти «методы получения и установки» и манипулировать структурой данных. Следовательно, почему я думаю о них больше как о принадлежностях и манипуляторах. Вы можете получить доступ к данным напрямую или положиться на другого члена семьи, чтобы получить эту часть права.

Защищенные добытчики / сеттеры

В защищенном контексте это не сильно отличается от публичного контекста. Зарубежные, возможно, мошеннические функции хотят получить доступ к структуре данных. Так что нет, если они существуют, они действуют как публичные добытчики / установщики.

Kain0_0
источник
0

Из опыта C ++, метод установки / получения полезен для 2 сценариев:

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

Помимо этого, безопасность является действительной точкой, но ее важность ограничена очень немногими приложениями для разработки, такими как модули входа или доступа к базе данных. Зачем беспокоиться о дополнительном кодировании, когда только несколько человек используют ваш модуль?

Аджай Нандория
источник