18

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

11

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

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

public String foo;

Чем это хуже, чем:

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

Первые варианты требуют значительно меньше шаблонного кода.

5 ответ(ов)

12

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

Вот некоторые из причин, о которых мне известно:

  • Инкапсуляция поведения, связанного с получением или установкой свойства — это позволяет более легко добавлять дополнительный функционал (например, валидацию) в будущем.
  • Скрытие внутреннего представления свойства при открытии свойства с использованием альтернативного представления.
  • Изоляция вашего публичного интерфейса от изменений — это позволяет публичному интерфейсу оставаться неизменным, в то время как реализация меняется, не затрагивая существующих пользователей.
  • Контроль за временем жизни и семантикой управления памятью (освобождением ресурсов) свойства — это особенно важно в средах с неуправляемой памятью (например, в C++ или Objective-C).
  • Обеспечение точки перехвата для отладки, когда свойство изменяется во время выполнения — отладка того, когда и где свойство изменилось на определенное значение, может быть довольно сложной без этого в некоторых языках.
  • Улучшенная совместимость с библиотеками, которые предназначены для работы с аксессорами (геттерами и сеттерами) — здесь можно упомянуть Mocking, Serialization и WPF.
  • Позволение наследникам изменять семантику того, как ведет себя и открывается свойство, переопределяя методы геттера/сеттера.
  • Позволение передавать геттеры и сеттеры как лямбда-выражения, а не значения.
  • Геттеры и сеттеры могут обеспечивать различные уровни доступа — например, метод получения может быть публичным, а метод установки — защищенным.
6

Поскольку через 2 недели (месяца, года) вы поймете, что вашему сеттеру нужно делать больше, чем просто устанавливать значение, вы также осознаете, что это свойство было использовано напрямую в 238 других классах 😃

4

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

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

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

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

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

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

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

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

Клиент: "Что я могу делать с объектом этого класса?"

Дизайнер: "Вы можете читать и записывать несколько переменных."

Клиент: "О, ... круто, наверное?"

Есть причины использовать геттеры и сеттеры, но если этих причин нет, создание пар геттеров и сеттеров под предлогом ложной инкапсуляции - это не хорошая практика. Хорошими причинами для создания геттеров или сеттеров могут быть те изменения, которые часто упоминаются, например, валидация или различные внутренние представления. Или, возможно, значение должно быть доступно для чтения клиентами, но не для записи (например, получение размера словаря), так что простой геттер будет хорошим выбором. Но эти причины должны присутствовать, когда вы делаете выбор, а не просто как потенциальное добавление позже. Это пример принципа YAGNI (You Ain't Gonna Need It).

1

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

Представьте, что вы просматриваете сотни строк кода и натыкаетесь на такой фрагмент:

person.name = "Joe";

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

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

0

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

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

Теперь представьте, что ВЕЗДЕ в вашем коде вы использовали публичное поле, и затем вы поняли, что вам нужно реализовать вышеуказанное требование. Приятного поиска каждого использования публичного поля вместо того, чтобы просто изменить ваш сеттер.

Мое мнение 😃

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь