Зачем использовать геттеры и сеттеры / средства доступа?

1387

Какое преимущество использования геттеров и сеттеров - которые только получают и устанавливают - вместо простого использования открытых полей для этих переменных?

Если получатели и сеттеры когда-либо делали больше, чем просто простой get/set, я могу понять это очень быстро, но я не понимаю на 100%, как:

public String foo;

хуже, чем:

private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }

В то время как первый принимает гораздо меньший шаблонный код.

  • 5
    @Dean J: дублируйся со многими другими вопросами: stackoverflow.com/search?q=getters+setters
  • 8
    Конечно, оба одинаково плохи, когда объект не нуждается в изменении свойства. Я бы предпочел сделать все конфиденциальным, а затем добавить геттеры, если это полезно, и сеттеры, если это необходимо.
Показать ещё 21 комментарий
Теги:
oop
abstraction
setter
getter

38 ответов

847
Лучший ответ

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

Вот некоторые из причин, о которых я знаю:

  • Инкапсуляция поведения, связанного с получением или настройкой свойства, - это позволяет добавить дополнительную функциональность (например, проверку) позже.
  • Скрытие внутреннего представления свойства при экспонировании свойства с использованием альтернативного представления.
  • Изоляция вашего открытого интерфейса от изменения - позволяя публичному интерфейсу оставаться постоянным, а реализация меняется, не затрагивая существующих потребителей.
  • Управление семантикой объекта управления временем жизни и памяти (удаление) - особенно важно в средах с неконтролируемой памятью (например, С++ или Objective-C).
  • Предоставление точки перехвата отладки при изменении свойства во время выполнения - отладка, когда и где свойство, измененное на конкретное значение, может быть довольно сложно без этого на некоторых языках.
  • Взаимодействие с библиотеками, которые предназначены для работы с атрибутами getter/seters - Mocking, Serialization и WPF, приходят на ум.
  • Предоставление наследникам возможности изменять семантику поведения объекта и подвергается переопределению методов getter/setter.
  • Разрешить использование getter/setter в качестве лямбда-выражений, а не значений.
  • Getters и seters могут разрешать разные уровни доступа - например, get может быть общедоступным, но набор может быть защищен.
  • 46
    +1. Просто добавить: разрешить ленивую загрузку. Разрешить копирование на запись.
  • 0
    @DaveJarvis: Как это связано с «Скажи, не спрашивай». Если поле открыто с помощью метода get и set, вы все равно можете попасть в ловушку «спрашивать, а не рассказывать».
Показать ещё 24 комментария
376

Из-за 2 недель (месяцев, лет), когда вы понимаете, что ваш сеттер должен сделать больше, чем просто установить значение, вы также поймете, что свойство было использовано непосредственно в 238 другие классы: -)

  • 73
    Я сижу и смотрю на приложение 500k line, где оно никогда не было нужно. Тем не менее, если бы это было необходимо однажды, это вызвало бы кошмар обслуживания. Достаточно хорошо для галочки для меня.
  • 19
    Я могу только позавидовать вам и вашему приложению :-) Тем не менее, это действительно зависит и от вашего программного стека. Например, Delphi (и C # - я думаю?) Позволяет вам определять свойства как граждан 1-го класса, где они могут читать / писать поля непосредственно на начальном этапе, но - если вам это нужно - также делайте это с помощью методов получения / установки. Мучо удобно. В Java, увы, нет - не говоря уже о стандарте javabeans, который также заставляет вас использовать методы получения / установки.
Показать ещё 23 комментария
313

Открытое поле не хуже пары геттеров/сеттеров, которая ничего не делает, кроме как вернуть поле и назначить ему. Во-первых, ясно, что (на большинстве языков) нет функциональных различий. Любая разница должна быть в других факторах, таких как ремонтопригодность или удобочитаемость.

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

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

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

Этот же аргумент применим и к другим предполагаемым преимуществам этих пассивных пар getter/setter: если позже вы решите изменить установленное значение, вы нарушите контракт. Если вы переопределяете функциональные возможности по умолчанию в производном классе, в отличие от нескольких безвредных модификаций (таких как ведение журнала или другое не наблюдаемое поведение), вы нарушаете контракт базового класса. Это является нарушением Принципа подстановки Лискова, который рассматривается как один из принципов ОО.

Если у класса есть эти немые геттеры и сеттеры для каждого поля, то это класс, который не имеет никаких инвариантов, без контракта. Это действительно объектно-ориентированный дизайн? Если у всех классов есть те геттеры и сеттеры, это просто немой держатель данных, а немые держатели данных должны выглядеть как немые держатели данных:

class Foo {
public:
    int DaysLeft;
    int ContestantNumber;
};

Добавление пассивных пар getter/setter в такой класс не добавляет значения. Другие классы должны обеспечивать значимые операции, а не только операции, которые уже предоставляют поля. Это то, как вы можете определять и поддерживать полезные инварианты.

Клиент: "Что я могу сделать с объектом этого класса?"
Дизайнер: "Вы можете читать и записывать несколько переменных".
Клиент: "О... круто, наверное?"

Есть причины использовать геттеры и сеттеры, но если этих причин не существует, то создание пар getter/setter во имя ложных инкапсулирующих богов не очень хорошо. Существенными причинами, побуждающими геттеры или сеттеры, являются те вещи, которые часто упоминаются как потенциальные изменения, которые вы можете сделать позже, например, валидация или различные внутренние представления. Или, может быть, значение должно читаться клиентами, но не записываться (например, читать размер словаря), поэтому простой геттер - это хороший выбор. Но эти причины должны быть там, когда вы делаете выбор, а не просто как потенциальную вещь, которую вы можете захотеть позже. Это пример YAGNI (вам это не понадобится).

  • 10
    Отличный ответ (+1). Моя единственная критика заключается в том, что мне потребовалось несколько чтений, чтобы выяснить, чем «валидация» в последнем абзаце отличалась от «валидации» в первых нескольких (которые вы выбросили в последнем случае, но способствовали в первом); корректировка формулировки может помочь в этом отношении.
  • 13
    Это отличный ответ, но, увы, нынешняя эпоха забыла, что такое «сокрытие информации» или для чего оно нужно. Они никогда не читали об неизменности и в поисках наиболее гибких, никогда не рисовали диаграмму состояний-переходов, которая определяла, какими были правовые состояния объекта и, следовательно, что нет.
Показать ещё 11 комментариев
76

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

Скажите, что вы просматриваете сотни строк кода, и вы сталкиваетесь с этим:

person.name = "Joe";

Это красиво просто кусок кода, пока вы не поймете его сеттер. Теперь вы следуете за этим установщиком и обнаруживаете, что он также устанавливает person.firstName, person.lastName, person.isHuman, person.hasReallyCommonFirstName и вызывает person.update(), который отправляет запрос в базу данных и т.д. О, это где произошла утечка памяти.

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

  • 7
    Да. В настоящее время рефакторинг большой базы кода, и это был кошмар. Получатели и установщики делают слишком много, включая вызов других очень занятых получателей и установщиков, которые в итоге сводят читабельность к нулю. Валидация является хорошей причиной для использования средств доступа, но использование их для выполнения гораздо большего, чем это, кажется, устраняет любую потенциальную выгоду.
  • 24
    Это аргумент против синтаксического сахара, а не против сеттеров вообще.
Показать ещё 4 комментария
45

В чистом объектно-ориентированном мире геттеры и сеттеры - это ужасный анти-шаблон. Прочтите эту статью: Getters/Setters. Зло. Период. В двух словах они поощряют программистов думать об объектах как о структурах данных, и этот тип мышления является чисто процедурным (например, в COBOL или C). В объектно-ориентированном языке нет структур данных, а только объекты, которые выставляют поведение (не атрибуты/свойства!)

Вы можете найти больше о них в разделе 3.5 Элегантные объекты (моя книга об объектно-ориентированном программировании).

  • 7
    Методы получения и установки предлагают модель анемичной области.
  • 5
    Интересная точка зрения. Но в большинстве случаев программирования нам нужны структуры данных. Возьмем пример «Собаки» из связанной статьи. Да, вы не можете изменить вес реальной собаки, установив атрибут ... но new Dog() - это не собака. Это объект, который содержит информацию о собаке. И для этого использования естественно иметь возможность исправлять неправильно записанный вес.
Показать ещё 6 комментариев
43

Есть много причин. Мой любимый - когда вам нужно изменить поведение или отрегулировать то, что вы можете установить для переменной. Например, скажем, у вас был метод setSpeed ​​(int speed). Но вы хотите, чтобы вы могли установить только максимальную скорость 100. Вы бы сделали что-то вроде:

public void setSpeed(int speed) {
  if ( speed > 100 ) {
    this.speed = 100;
  } else {
    this.speed = speed;
  }
}

Теперь, что, если EVERYWHERE в вашем коде вы использовали публичное поле, а затем поняли, что вам нужно вышеуказанное требование? Удачи, охотясь за каждым использованием открытого поля вместо того, чтобы просто изменять ваш сеттер.

Мои 2 цента:)

  • 54
    Охота за каждым использованием публичного поля не должна быть такой сложной. Сделайте это приватным и позвольте компилятору найти их.
  • 11
    это правда, конечно, но зачем делать это сложнее, чем было задумано. Подход «получить / установить» - все еще лучший ответ.
Показать ещё 12 комментариев
36

Одним из преимуществ аксессуаров и мутаторов является то, что вы можете выполнить проверку.

Например, если foo был общедоступным, я мог бы легко установить его на null, а затем кто-то другой мог попытаться вызвать метод для объекта. Но его уже нет! С помощью метода setFoo я мог убедиться, что foo никогда не был установлен в null.

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

29

Зависит от вашего языка. Вы отметили эту "объектно-ориентированную", а не "Java", поэтому я хотел бы указать, что ответ ChssPly76 зависит от языка. В Python, например, нет причин использовать геттеры и сеттеры. Если вам нужно изменить поведение, вы можете использовать свойство, которое обертывает getter и setter вокруг доступа к базовому атрибуту. Что-то вроде этого:

class Simple(object):
   def _get_value(self):
       return self._value -1

   def _set_value(self, new_value):
       self._value = new_value + 1

   def _del_value(self):
       self.old_values.append(self._value)
       del self._value

   value = property(_get_value, _set_value, _del_value)
  • 2
    Да, я сказал так же в комментарии ниже моего ответа. Java - не единственный язык, который использует геттеры / сеттеры в качестве опоры, точно так же, как Python - не единственный язык, способный определять свойства. Главное, однако, все еще остается - «собственность» - это не то же самое «публичное поле».
  • 0
    @ ChssPly76: но главная причина использования геттеров и сеттеров состоит в том, чтобы избежать необходимости менять интерфейс, когда вы хотите добавить больше функциональности, а это означает, что совершенно приемлемо - даже идиоматично - использовать открытые поля, пока такая функциональность не потребуется.
Показать ещё 4 комментария
24

Ну, я просто хочу добавить, что даже если они иногда необходимы для инкапсуляции и безопасности ваших переменных/объектов, если мы хотим закодировать реальную объектно-ориентированную программу, тогда нам нужно ОСТАНОВИТЬ ОШИБКИ АКСЕССУАРОВ, потому что иногда мы очень сильно зависим от них, когда это действительно не нужно, и это почти так же, как если бы мы публиковали переменные.

22

Я знаю это немного позже, но я думаю, что есть люди, которые заинтересованы в производительности.

Я сделал небольшой тест производительности. Я написал класс "NumberHolder", который, ну, имеет целое число. Вы можете прочитать это Integer с помощью метода getter anInstance.getNumber() или путем прямого доступа к числу с помощью anInstance.number. Моя программа читает номер 1,000,000,000 раз, в обоих направлениях. Этот процесс повторяется пять раз и время печатается. Я получил следующий результат:

Time 1: 953ms, Time 2: 741ms
Time 1: 655ms, Time 2: 743ms
Time 1: 656ms, Time 2: 634ms
Time 1: 637ms, Time 2: 629ms
Time 1: 633ms, Time 2: 625ms

(Время 1 - это прямой путь, время 2 - геттер)

Вы видите, что геттер (почти) всегда немного быстрее. Затем я пробовал с различным количеством циклов. Вместо 1 миллиона я использовал 10 миллионов и 0,1 миллиона. Результаты:

10 миллионов циклов:

Time 1: 6382ms, Time 2: 6351ms
Time 1: 6363ms, Time 2: 6351ms
Time 1: 6350ms, Time 2: 6363ms
Time 1: 6353ms, Time 2: 6357ms
Time 1: 6348ms, Time 2: 6354ms

С 10 миллионами циклов время почти одинаковое. Вот 100 тысяч (0,1 миллиона) циклов:

Time 1: 77ms, Time 2: 73ms
Time 1: 94ms, Time 2: 65ms
Time 1: 67ms, Time 2: 63ms
Time 1: 65ms, Time 2: 65ms
Time 1: 66ms, Time 2: 63ms

Также с разным количеством циклов геттер немного быстрее обычного. Надеюсь, это вам помогло.

  • 2
    Есть «заметные» накладные расходы, имеющие вызов функции для доступа к памяти вместо простой загрузки адреса объекта и добавления смещения для доступа к членам. Скорее всего, виртуальная машина все равно оптимизирует ваш геттер. Несмотря на это, упомянутые накладные расходы не стоят того, чтобы терять все преимущества геттеров / сеттеров.
22

Спасибо, это действительно прояснило мое мышление. Теперь вот (почти) 10 (почти) веских причин НЕ использовать геттеры и сеттеры:

  • Когда вы понимаете, что вам нужно сделать больше, чем просто установить и получить значение, вы можете просто сделать поле частным, что сразу сообщит вам, где вы напрямую обращались к нему.
  • Любая проверка, которую вы выполняете, может быть только контекстной, что редко бывает на практике.
  • Вы можете изменить установленное значение - это абсолютный кошмар, когда вызывающий абонент передает вам значение, которое они [shock horror] хотят, чтобы вы сохранили AS IS.
  • Вы можете скрыть внутреннее представление - фантастическое, так что вы убедитесь, что все эти операции симметричны правильно?
  • Вы изолировали свой публичный интерфейс от изменений под листами - если вы разрабатывали интерфейс и не были уверены, что прямой доступ к чему-то был в порядке, то вы должны были продолжать проектирование.
  • Некоторые библиотеки ожидают этого, но не так много - отражение, сериализация, mock-объекты все отлично работают с публичными полями.
  • Наследуя этот класс, вы можете переопределить функции по умолчанию - другими словами, вы можете ДЕЙСТВИТЕЛЬНО путать вызывающих, не только скрывая реализацию, но и делая ее несовместимой.

Последние три я ухожу (N/A или D/C)...

  • 11
    Я думаю, что ключевой аргумент заключается в том, что «если вы проектировали интерфейс и не были уверены, что прямой доступ к чему-либо был в порядке, то вы должны были продолжать проектировать». Это самая важная проблема с геттерами / сеттерами: они сводят класс к простому контейнеру (более или менее) открытых полей. Однако в реальном ООП объект - это больше, чем контейнер полей данных. Он инкапсулирует состояние и алгоритмы для управления этим состоянием. Что важно в этом утверждении, так это то, что состояние должно быть инкапсулировано и управляться только алгоритмами, предоставляемыми объектом.
15

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

Подумайте просто, легко, добавьте сложности, когда это необходимо.

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

У меня массивная система, написанная без геттерных сеттеров только с модификаторами доступа и некоторыми методами для проверки n выполнения логики biz. Если вам это абсолютно необходимо. Используйте что-нибудь.

14

Мы используем геттеры и сеттеры:

  • для повторного использования
  • для выполнения проверки на последующих этапах программирования

Методы Getter и setter представляют собой общедоступные интерфейсы для доступа к частным членам класса.


Мантрация инкапсуляции

Мандат инкапсуляции должен делать частные и общедоступные поля.

Методы Getter: Мы можем получить доступ к закрытым переменным.

Методы сеттера: Мы можем изменить частные поля.

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

  • лучше
  • безопаснее; и
  • быстрее.

В любом случае можно использовать значение, возвращающее это значение. Вместо:

int x = 1000 - 500

использование

int x = 1000 - class_name.getValue();

В условиях неспециалиста

Изображение 1330

Предположим, нам нужно сохранить детали этого Person. Этот Person имеет поля name, age и sex. Для этого необходимо создать методы для name, age и sex. Теперь, если нам нужно создать другого человека, необходимо снова создать методы для name, age, sex.

Вместо этого мы можем создать bean class(Person) с помощью методов getter и setter. Поэтому завтра мы можем просто создавать объекты этого bean class(Person class), когда нам нужно добавить нового человека (см. Рисунок). Таким образом, мы повторно используем поля и методы класса bean, что намного лучше.

14

Я долгое время думал об этом для Java-приложения, и я считаю, что настоящими причинами являются:

  • Код для интерфейса, а не реализация
  • Интерфейсы задают только методы, а не поля

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

Эти методы - позорный геттер и сеттер....

  • 1
    Хорошо, второй вопрос; в случае, когда это проект, когда вы никому не экспортируете источник, и у вас есть полный контроль над источником ... вы получаете что-нибудь с помощью геттеров и сеттеров?
  • 2
    В любом нетривиальном проекте Java вам нужно кодировать интерфейсы, чтобы сделать вещи управляемыми и тестируемыми (например, макеты и прокси-объекты). Если вы используете интерфейсы, вам нужны геттеры и сеттеры.
14

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

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

Я перешел из общедоступных полей в getters, и теперь геттеры проверяют кеш, и если он не вызывает веб-службу. Таким образом, с небольшой упаковкой было предотвращено множество вызовов веб-сервисов.

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

    protected YourType _yourName = null;
    public YourType YourName{
      get
      {
        if (_yourName == null)
        {
          _yourName = new YourType();
          return _yourName;
        }
      }
    }
  • 0
    Так что же, получатель вызывает установщик?
  • 0
    Я добавил пример кода того, как я это делал в прошлом - по сути, вы сохраняете фактический класс в защищенном элементе, а затем возвращаете этот защищенный элемент в метод доступа get, инициализируя его, если он не инициализирован.
Показать ещё 2 комментария
12

Один из аспектов, который я пропустил в ответах до сих пор, спецификация доступа:

  • для членов у вас есть только одна спецификация доступа для настройки и получения
  • для сеттеров и геттеров вы можете настроить его и определить отдельно.
10

В языках, которые не поддерживают "свойства" (С++, Java) или требуют перекомпиляции клиентов при изменении полей в свойствах (С#), использование методов get/set легче модифицировать. Например, добавление логики проверки в метод setFoo не требует изменения общедоступного интерфейса класса.

В языках, поддерживающих "реальные" свойства (Python, Ruby, возможно, Smalltalk?) нет смысла получать/устанавливать методы.

  • 1
    Re: C #. Если вы добавите функциональность в get / set, не потребует ли это перекомпиляции?
  • 0
    @ Steamer25: извините, неправильно набрал. Я имел в виду, что клиенты класса должны будут перекомпилироваться.
Показать ещё 4 комментария
9

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

Двумя основными являются полиморфизм и валидация. Даже если это просто глупая структура данных.

Скажем, у нас есть этот простой класс:

public class Bottle {
  public int amountOfWaterMl;
  public int capacityMl;
}

Очень простой класс, в котором содержится количество жидкости в нем и какова его емкость (в миллилитрах).

Что происходит, когда я делаю:

Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacity = 1000;

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

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

Мы получим что-то вроде:

public interface LiquidContainer {
  public int getAmountMl();
  public void setAmountMl(int amountMl);
  public int getCapacityMl();
  public void setCapcityMl(int capacityMl);
}

Отлично! И теперь мы просто меняем бутылку на это:

public class Bottle extends LiquidContainer {
  private int capacityMl;
  private int amountFilledMl;
  public Bottle(int capacityMl, int amountFilledMl) {
    this.capacityMl = capacityMl;
    this.amountFilledMl = amountFilledMl;
    checkNotOverFlow();
  }

  public int getAmountMl() {
    return amountFilledMl;
  }

  public void setAmountMl(int amountMl) {
     this.amountFilled = amountMl;
     checkNotOverFlow();
  }
  public int getCapacityMl() {
    return capacityMl;
  public void setCapcityMl(int capacityMl) {
    this.capacityMl = capacityMl;
    checkNotOverFlow();
  }

  private void checkNotOverFlow() {
    if(amountOfWaterMl > capacityMl) {
      throw new BottleOverflowException();
    }
}

Я оставлю определение BottleOverflowException в качестве упражнения для читателя.

Теперь обратите внимание, насколько это более устойчиво. Теперь мы можем иметь дело с контейнером любого типа в нашем коде, принимая LiquidContainer вместо Bottle. И как эти бутылки справляются с такими вещами, все могут различаться. У вас могут быть бутылки, которые записывают свое состояние на диск, когда они меняются, или бутылки, которые сохраняются в базах данных SQL, или GNU знает, что еще.

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

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

Там также третье, что не все обратились: Getters и seters используют вызовы методов. Это означает, что они выглядят как обычные методы везде. Вместо того, чтобы иметь странный специфический синтаксис для DTO и прочее, вы везде одинаковы.

  • 2
    Спасибо за первое полуприличное объяснение интерфейсов, которые я прочитал, без каких-либо ссылок на «пульты дистанционного управления» или «автомобили»
  • 1
    «У вас могут быть бутылки, которые записывают свое состояние на диск при его изменении, или бутылки, которые сохраняются в базах данных SQL», - так я хохотал xD. Но в любом случае, хорошая идея представить это!
6

Один из основных принципов проектирования OO: Инкапсуляция!

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

  • 22
    Геттеры и сеттеры для инкапсуляции очень смешные. Смотрите здесь .
  • 0
    Если ваш публичный интерфейс заявляет, что «foo» имеет тип «T» и может быть установлен на что угодно, вы никогда не сможете это изменить. В последнем случае вы не можете принять решение сделать его типом «Y», а также не можете навязывать правила, такие как ограничения размера. Таким образом, если у вас есть общедоступный метод get / set, который ничего не делает для установки / получения, вы не получаете ничего, что не будет предлагать публичное поле, что делает его более громоздким в использовании. Если у вас есть ограничение, например, объект может быть установлен в значение null или значение должно находиться в пределах диапазона, тогда да, потребуется публичный метод set, но вы все равно представите контракт на установку этого значения, которое может ' т изменить
Показать ещё 2 комментария
4

Вы должны использовать геттеры и сеттеры, когда:

  • Вы имеете дело с чем-то, что является концептуально атрибутом, но:
    • У вашего языка нет свойств (или какого-либо подобного механизма, например, трассировки переменных Tcl) или
    • Для этого варианта использования недостаточно поддержки языковой поддержки или
    • Идиоматические соглашения вашего языка (или иногда ваши рамки) поощряют геттеры или сеттеры для этого варианта использования.

Итак, это очень редко общий вопрос ОО; это вопрос, специфичный для языка, с различными ответами на разные языки (и различные варианты использования).


С точки зрения теории ОО геттеры и сеттеры бесполезны. Интерфейс вашего класса - это то, что он делает, а не то, что его состояние. (Если нет, вы написали неправильный класс.) В очень простых случаях, когда то, что делает класс, просто, например, представляет точку в прямоугольных координатах, * атрибуты являются частью интерфейса; геттеры и сеттеры просто облака, что. Но во всех, кроме очень простых случаях, ни атрибуты, ни геттеры, ни сеттеры не являются частью интерфейса.

Положите другой путь: если вы считаете, что потребители вашего класса не должны даже знать, что у вас есть атрибут spam, а тем более неспособность изменить его волей-неволей, то предоставление им метода set_spam - это последнее, что вы хотите сделать.

* Даже для этого простого класса вы можете не обязательно указывать значения x и y. Если это действительно класс, не должны ли он иметь такие методы, как translate, rotate и т.д.? Если это только класс, потому что у вашего языка нет записей /structs/named tuples, то на самом деле это не вопрос OO...


Но никто никогда не делает общий дизайн OO. Они выполняют дизайн и реализацию на определенном языке. И на некоторых языках геттеры и сеттеры далеки от бесполезности.

Если ваш язык не имеет свойств, то единственный способ представить что-то, что концептуально является атрибутом, но на самом деле вычисляется, или проверяется, и т.д., происходит через getters и seters.

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

Что касается "что, если я хочу позже изменить свою реализацию?" вопрос (который повторяется несколько раз в разных формулировках как в вопросе OP, так и в принятом ответе): если это действительно чистое изменение реализации, и вы начали с атрибута, вы можете изменить его на свойство без влияния на интерфейс. Если, конечно, ваш язык не поддерживает это. Так что это действительно тот же случай снова.

Также важно следить за идиомами языка (или рамки), который вы используете. Если вы напишете красивый код стиля Ruby на С#, любой опытный разработчик С#, отличный от вас, будет иметь проблемы с его чтением, и это плохо. Некоторые языки имеют более сильные культуры вокруг своих соглашений, чем другие. - И не может быть совпадением, что Java и Python, которые находятся на противоположных концах спектра для того, как идиоматические геттеры, имеют две из самых сильных культур.

Помимо человеческих читателей, будут библиотеки и инструменты, которые ожидают, что вы будете следовать конвенциям и сделайте свою жизнь более сложной, если вы этого не сделаете. Виджеты Builder Interface Builder для чего-либо, кроме свойств ObjC, или с использованием некоторых Java-mocking-библиотек без геттеров, просто усложняют вашу жизнь. Если инструменты важны для вас, не сражайтесь с ними.

4

С точки зрения объектной ориентации обе альтернативы могут повредить поддержанию кода, ослабляя инкапсуляцию классов. Для обсуждения вы можете изучить эту замечательную статью: http://typicalprogrammer.com/?p=23

3

Есть веская причина, чтобы рассмотреть возможность использования accessors - нет наследования свойств. См. Следующий пример:

public class TestPropertyOverride {
    public static class A {
        public int i = 0;

        public void add() {
            i++;
        }

        public int getI() {
            return i;
        }
    }

    public static class B extends A {
        public int i = 2;

        @Override
        public void add() {
            i = i + 2;
        }

        @Override
        public int getI() {
            return i;
        }
    }

    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.i);
        a.add();
        System.out.println(a.i);
        System.out.println(a.getI());
    }
}

Вывод:

0
0
4
3

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

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

int getVar() const { return var ; }

Итак, у вас есть:

doSomething( obj->getVar() ) ;

Вместо

doSomething( obj->var ) ;

Не только getVar() визуально шумно, он дает иллюзию, что gettingVar() является каким-то более сложным процессом, чем он есть на самом деле. Как вы (как писатель класса) относитесь к святости var, особенно сбивает с толку пользователь вашего класса, если у него есть сеттер паушру - тогда похоже, что вы пытаетесь заставить эти ворота "защищать" то, что вы настаиваете (святость var), но даже вы соглашаетесь с тем, что защита не стоит того, чтобы кто-либо мог просто войти и set var к любой ценности, которую они хотят, без вас глядя на то, что они делают.

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

1) Начните со всех открытых элементов для базовых объектов с данными и поведением. Вот почему во всем моем "примерном" коде С++ вы заметите, что я использую struct вместо class всюду.

2) Когда внутреннее поведение объекта для элемента данных становится достаточно сложным (например, ему нравится поддерживать внутренний std::list в некотором порядке), записываются функции типа доступа. Поскольку я программирую сам по себе, я не всегда устанавливаю член private сразу, но где-то по эволюции класса член будет "продвигаться" на protected или private.

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

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

  • 1
    «... четко определенный интерфейс, который вы не можете просто испортить с внутренностями» и проверка в установщиках.
3

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

Есть несколько преимуществ использования методов getter и setter, таких как возможность создавать элементы со сложными функциональными возможностями, к которым вы можете получить доступ, например, к свойствам. Они также позволяют создавать свойства только для чтения и только для записи.

Несмотря на то, что методы getter и setter полезны, вы должны быть осторожны, чтобы не злоупотреблять ими, потому что, помимо прочих проблем, они могут затруднить обслуживание кода в определенных ситуациях. Кроме того, они предоставляют доступ к вашей реализации класса, например, к публичным пользователям. Практика ООП препятствует прямому доступу к свойствам внутри класса.

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

2

Одним из относительно современных преимуществ getters/seters является то, что упрощает просмотр кода в тегированных (индексированных) редакторах кода. Например. Если вы хотите узнать, кто устанавливает член, вы можете открыть иерархию вызовов сеттера.

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

2

Getters и настройки используются для реализации двух основных аспектов объектно-ориентированного программирования:

  • Абстракция
  • Герметизация

Предположим, что у нас есть класс Employee:

package com.highmark.productConfig.types;

public class Employee {

    private String firstName;
    private String middleName;
    private String lastName;

    public String getFirstName() {
      return firstName;
    }
    public void setFirstName(String firstName) {
       this.firstName = firstName;
    }
    public String getMiddleName() {
        return middleName;
    }
    public void setMiddleName(String middleName) {
         this.middleName = middleName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName(){
        return this.getFirstName() + this.getMiddleName() +  this.getLastName();
    }
 }

Здесь детали реализации полного имени скрыты от пользователя и недоступны непосредственно пользователю, в отличие от общедоступного атрибута.

  • 1
    Для меня иметь тонны добытчиков и сеттеров, которые не делают ничего уникального, бесполезно. getFullName является исключением, потому что он делает что-то еще. Если у вас есть только три открытые переменные, то сохранение getFullName сделает программу более удобной для чтения, но при этом скрытое имя будет полностью скрыто. Как правило, я полностью согласен с геттерами и сеттерами, если. они делают что-то уникальное и / или б. у вас есть только один, да, вы могли бы иметь публичный финал и все такое, но нет
  • 1
    Преимущество этого состоит в том, что вы можете изменять внутренние компоненты класса, не меняя интерфейс. Скажем, вместо трех свойств у вас было одно - массив строк. Если вы использовали геттеры и сеттеры, вы можете внести это изменение, а затем обновить геттер / сеттеры, чтобы они знали, что names [0] - это имя, names [1] - это середина и т. Д. Но если вы просто использовали публичные свойства Кроме того, вам придется изменить каждого сотрудника, к которому обращался класс, поскольку используемого им свойства firstName больше не существует.
2

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

2

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

  • 0
    Я бы никогда не ожидал, что метод получения или установки будет дорогостоящей операцией. В таких случаях лучше использовать фабрику: используйте все необходимые сеттеры, а затем вызовите дорогой метод execute или build .
1

Getters и сеттеры, исходящие из скрытия данных. Скрытие данных означает, что мы скрывать данные от посторонних или за пределами лица/вещи не могут получить доступ наши данные. Это полезная функция в ООП.

В качестве примера:

Если вы создаете общедоступную переменную, вы можете получить доступ к этой переменной и изменить значение в любом месте (любой класс). Но если вы создадите в качестве частного эту переменную, она не сможет увидеть/получить доступ в любом классе, кроме объявленного класса.

public и private являются модификаторами доступа.

Итак, как мы можем получить доступ к этой переменной снаружи:

Это место getters и сеттеры. Вы можете объявить переменную как private, тогда вы можете реализовать getter и setter для этой переменной.

Пример (Java):

private String name;

public String getName(){
   return this.name;
}

public void setName(String name){
   this.name= name;
}

Преимущество:

Когда кто-либо хочет получить доступ или изменить/установить значение переменной balance, он должен иметь разрешение.

//assume we have person1 object
//to give permission to check balance
person1.getName()

//to give permission to set balance
person1.setName()

Вы также можете установить значение в конструкторе, но когда позже, когда захотите для обновления/изменения значения вы должны реализовать метод setter.

  • 0
    ваш getBalance / setBalance не являются getter / setter, если у них есть другой код, не так ли? И зачем выставлять баланс? Разве не было бы безопаснее иметь applyPayment или applyDebt, который позволял бы проверять баланс и, возможно, поле для напоминания, чтобы сказать, откуда был получен платеж? Привет! Я только улучшил ваш дизайн И удалил сеттер и геттер. В том-то и дело, что в setters / getters всегда улучшается ваш код, чтобы удалить их, не то, что они «неправильные», просто они почти всегда приводят к ухудшению кода. Свойства (как в C #), кстати, имеют точно такую же проблему.
1

Я могу думать о одной причине, почему вы не просто хотите, чтобы все было публично.

Например, к переменной, которой вы никогда не предназначались для использования вне класса, можно было бы получить доступ, даже прямо через доступ к цепочке переменных (т.е. object.item.origin.x).

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

  • 0
    Яркий пример этого - когда значение одного атрибута влияет на одно или несколько других значений атрибута. Тривиальный пример: скажем, вы сохраняете радиус круга. Использование circ.set_radius () `на самом деле ничего не добивается, устанавливая радиус непосредственно через circ.radius . Однако get_diameter (), get_circumference () и get_area () могут выполнять вычисления на основе радиуса. Без добытчиков вы должны выполнить расчеты самостоятельно и убедиться, что вы их правильно поняли.
1

Я хотел бы просто бросить идею аннотации: @getter и @setter. С помощью @getter вы должны иметь возможность obj = class.field, но не class.field = obj. С @setter, наоборот. С @getter и @setter вы должны быть в состоянии сделать то и другое. Это позволит сохранить инкапсуляцию и сократить время, не вызывая тривиальные методы во время выполнения.

  • 1
    Это будет реализовано во время выполнения с "тривиальными методами". На самом деле, наверное, нетривиально.
  • 0
    Сегодня это можно сделать с помощью препроцессора аннотации.
1

Кроме того, это означает "будущий" класс. В частности, переход с поля на свойство - это разрыв ABI, поэтому, если вы позже решите, что вам нужна больше логики, чем просто "установить/получить поле", тогда вам нужно сломать ABI, что, конечно, создает проблемы для чего-либо else уже скомпилирован против вашего класса.

  • 2
    Я полагаю, что изменение поведения геттера или сеттера не является серьезным изменением. </ Сарказм>
  • 0
    @ R.MartinhoFernandes не всегда должен быть. TBYS
0

Хотя это не является общим для getter и setter, использование этих методов также может использоваться в использовании AOP/proxy. например, для проверки переменной вы можете использовать AOP для аудита обновления любого значения. Без getter/setter это невозможно, кроме изменения кода везде. Personaly Я никогда не использовал AOP для этого, но он показывает еще одно преимущество использования getter/setter.

0

Одним из относительно современных преимуществ getters/seters является то, что упрощает просмотр кода в тегированных (индексированных) редакторах кода. Например. Если вы хотите узнать, кто устанавливает член, вы можете открыть иерархию вызовов сеттера.

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

0

Если вы хотите переменную readonly, но не хотите, чтобы клиент мог изменить способ доступа к ней, попробуйте этот шаблонный класс:

template<typename MemberOfWhichClass, typename primative>                                       
class ReadOnly {
    friend MemberOfWhichClass;
public:
    template<typename number> inline bool   operator==(const number& y) const { return x == y; } 
    template<typename number> inline number operator+ (const number& y) const { return x + y; } 
    template<typename number> inline number operator- (const number& y) const { return x - y; } 
    template<typename number> inline number operator* (const number& y) const { return x * y; }  
    template<typename number> inline number operator/ (const number& y) const { return x / y; } 
    template<typename number> inline number operator<<(const number& y) const { return x << y; }
    template<typename number> inline number operator^(const number& y) const  { return x^y; }
    template<typename number> inline number operator~() const                 { return ~x; }
    template<typename number> inline operator number() const                  { return x; }
protected:
    template<typename number> inline number operator= (const number& y) { return x = y; }       
    template<typename number> inline number operator+=(const number& y) { return x += y; }      
    template<typename number> inline number operator-=(const number& y) { return x -= y; }      
    template<typename number> inline number operator*=(const number& y) { return x *= y; }      
    template<typename number> inline number operator/=(const number& y) { return x /= y; }      
    primative x;                                                                                
};      

Пример использования:

class Foo {
public:
    ReadOnly<Foo, int> cantChangeMe;
};

Помните, что вам нужно добавить и побитовые и унарные операторы! Это просто, чтобы вы начали.

  • 0
    Обратите внимание, что вопрос был задан без учета конкретного языка.
0

Я позволю коду говорить сам за себя:

Mesh mesh = new Mesh();
BoundingVolume vol = new BoundingVolume();
mesh.boundingVolume = vol;
vol.mesh = mesh;
vol.compute(); 

Вам нравится? Вот с сеттерами:

Mesh mesh = new Mesh();
BoundingVolume vol = new BoundingVolume();
mesh.setBoundingVolume(vol);
  • 3
    Вы не ответили на вопрос: «В чем преимущество использования геттеров и сеттеров - это только получение и установка ...» Ваш пример не является этим случаем, и поэтому ваш ответ не относится к этому вопросу.
  • 0
    Я думаю, что семантика данных примеров кода совершенно другая. В первом примере также создается круговая ссылка: mesh.boundingVolume.mesh == mesh
0

Я хотел опубликовать пример реального мира, который я только что закончил:

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

package com.foo.entities.custom
class User extends com.foo.entities.User{
     public Integer getSomething(){
         return super.getSomething();             
     }
     public void setSomething(Integer something){
         something+=1;
         super.setSomething(something); 
     }
}

То, что я сделал выше, переопределяет существующие методы суперкласса с моей новой функциональностью (что-то + 1), не касаясь базового класса. Тот же сценарий, если вы написали класс год назад и хотите перейти на версию 2 без изменения базовых классов (пробный кошмар). надеюсь, что это поможет.

  • 4
    «То, что я сделал выше, переопределило существующие методы суперкласса с моей новой функциональностью». С функциональностью, которая, как мы все надеемся, уже была правильно задокументирована для старого интерфейса, верно? В противном случае вы просто нарушили LSP и внесли скрытые изменения, которые были бы обнаружены компилятором, если бы в поле зрения не было методов получения / установки.
  • 0
    @ R.MartinhoFernandes "... и затем запускать инструменты гибернации для генерации кода Java" это не случай изменения поведения некоторого класса, это обходной путь до дерьмового инструмента. Возможно, контракт (который в данном случае входит в подкласс) всегда был для этого «сюрприза». Может быть аргументом для has-a вместо is-a.
Показать ещё 1 комментарий
-1

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

public class foo
{
    public int f1 { get; set; }             // A classic GS
    public int f2 { get; private set; }     // A GS with public read access, the write is only on the private level
    public int f3 { private get; set; }     // A GS where "You can set, but you can't get" outside the class
    public int f4 { get; set; } = 10;       // A GS with default value, this is a NEW feature of C# 6.0 / .NET 4.6
}
  • 0
    В Java у вас нет таких методов получения и установки. То, что у вас есть, это Project Lombok, который использует аннотации для почти того же эффекта. Так что, если вы хотите эту милость, но в Java, а не в C #, то вы знаете, куда идти. :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню