Можно ли отказаться от методов получения и установки простых классов?
Я делаю очень простой класс для представления позиций в трехмерном пространстве.
В настоящее время я просто даю пользователю доступ и изменяю 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 хорошо понятны
- обратите внимание, что, проявляя осторожность, можно получить прямой доступ к переменным-членам при использовании
typedef
s для их типов, так что прокси-объект может быть заменен и поддерживать идентичное использование клиентского кода при вызове другой реализации
- обратите внимание, что, проявляя осторожность, можно получить прямой доступ к переменным-членам при использовании
Это хорошо для такой хорошо известной структуры, которая:
- Может иметь любые возможные значения, например, int;
- Должен работать как встроенный тип при манипулировании его значением по соображениям производительности.
Однако, если вам нужно больше, чем тип, который "просто является трехмерным вектором", то вы должны заключить его в другой класс, как закрытый член, который бы затем представлял x, y и z через функции-члены и функции-члены дополнительных функций.
Если вы делаете что-то очень простое, ваше решение может быть просто отличным.
Если позже вы поймете, что вычисления в сферической системе координат намного проще или быстрее (и вам нужна производительность), вы можете рассчитывать на этот удар.
Причиной этого является то, что, поскольку NumericType является параметром шаблона, я не могу полагаться на то, что существует достойный способ проверки значений на предмет работоспособности. (Как я узнаю, что пользователь не захочет, чтобы позиция была представлена отрицательными значениями?)
Язык и компиляторы хорошо поддерживают этот случай (через специализацию).
Следовательно, нет смысла добавлять геттеры или сеттеры для усложнения интерфейса, и прямой доступ должен быть предпочтительным для его краткости.
Спорный аргумент - см. Выше.
Это хорошее исключение из хорошей практики?
Я не думаю, что это так. Ваш вопрос подразумевает, что проверка должна существовать, но ее не стоит реализовывать / поддерживать, потому что вы решили использовать template
в вашей реализации, а не специализироваться в соответствии с выбранной вами языковой функцией. При таком подходе интерфейс кажется только частично поддерживаемым - эти недостающие реализации будут просто загрязнять реализации клиентов.