Можно ли отказаться от методов получения и установки простых классов?

Я делаю очень простой класс для представления позиций в трехмерном пространстве.

В настоящее время я просто даю пользователю доступ и изменяю X, Y а также Z значения напрямую. Другими словами, они являются открытыми переменными-членами.

template <typename NumericType = double>
struct Position
{
    NumericType X, Y, Z;

    // Constructors, operators and stuff...
};

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

Pos.X = Pos.Y + Pos.Z; // Versus...
Pos.SetX(Pos.GetY() + Pos.GetZ());

Это хорошее исключение из хорошей практики? Будет ли (гипотетический) будущий разработчик моего кода выследить меня и ударить меня по лицу?

6 ответов

Решение

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

Распространенные причины использования сеттера (возможно, есть и другие):

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

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

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

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

Если вы действительно хотите сделать элемент данных приватным, но при этом предоставить ему полный доступ, просто сделайте одну функцию доступа перегруженной один раз, как T & access() и как только const T & access() const,


Редактировать: в недавнем проекте я просто использовал кортежи для координат с глобальными функциями доступа. Возможно, это может быть полезно:

template <typename T>
inline T cX(const std::tuple<T,T,T> & t) { return std::get<0>(t); }

typedef std::tuple<double, double, double> coords;
//template <typename T> using coords = std::tuple<T,T,T>; // if I had GCC 4.8

coords c{1.2, -3.4, 5.6};

// Now we can access cX(c), cY(c), cZ(c).

Это заняло у меня некоторое время, но я проследил это старое интервью Страуструпа, где он сам обсуждает структуры открытых данных и инкапсулированные классы: http://www.artima.com/intv/goldilocks3.html

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

  • перекомпиляция / зависимость от ссылки: низкоуровневый библиотечный код, который используется большим количеством приложений, где эти приложения могут занимать много времени и / или их трудно перекомпилировать и повторно развертывать
    • Обычно это проще, если реализация была нестандартной (что может потребовать идиомы pImpl и компромиссов производительности), поэтому вам нужно только выполнить повторное связывание, и еще проще, если вы можете развернуть новые общие библиотеки и просто отказаться от приложения
    • В отличие от этого, инкапсуляция дает значительно меньшую выгоду, если объект используется только вextern"реализация конкретного переводческого блока
  • стабильность интерфейса, несмотря на нестабильность реализации: код, в котором реализация более экспериментальная / нестабильная, но требования API хорошо понятны
    • обратите внимание, что, проявляя осторожность, можно получить прямой доступ к переменным-членам при использовании typedefs для их типов, так что прокси-объект может быть заменен и поддерживать идентичное использование клиентского кода при вызове другой реализации

Это хорошо для такой хорошо известной структуры, которая:

  1. Может иметь любые возможные значения, например, int;
  2. Должен работать как встроенный тип при манипулировании его значением по соображениям производительности.

Однако, если вам нужно больше, чем тип, который "просто является трехмерным вектором", то вы должны заключить его в другой класс, как закрытый член, который бы затем представлял x, y и z через функции-члены и функции-члены дополнительных функций.

Если вы делаете что-то очень простое, ваше решение может быть просто отличным.

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

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

Язык и компиляторы хорошо поддерживают этот случай (через специализацию).

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

Спорный аргумент - см. Выше.

Это хорошее исключение из хорошей практики?

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

Другие вопросы по тегам