Во время недавнего проекта, над которым я работал, мне пришлось использовать множество функций, которые выглядят примерно так:
static bool getGPS(double plane_latitude, double plane_longitude, double plane_altitude,
double plane_roll, double plane_pitch, double plane_heading,
double gimbal_roll, double gimbal_pitch, double gimbal_yaw,
int target_x, int target_y, double zoom,
int image_width_pixels, int image_height_pixels,
double & Target_Latitude, double & Target_Longitude, double & Target_Height);
Поэтому я хочу изменить его, чтобы он выглядел примерно так:
static GPSCoordinate getGPS(GPSCoordinate plane, Angle3D planeAngle, Angle3D gimbalAngle,
PixelCoordinate target, ZoomLevel zoom, PixelSize imageSize)
Мне кажется, это гораздо более читабельно и безопасно, чем первый метод. Но имеет ли смысл создавать PixelCoordinate
и PixelSize
занятия? Или мне лучше использовать только std::pair<int,int>
для каждого. И имеет ли смысл иметь ZoomLevel
класс, или я должен просто использовать double
?
Моя интуиция в использовании классов для всего основана на следующих предположениях:
- Если есть классы для всего, было бы невозможно передать туда,
ZoomLevel
гдеWeight
ожидался объект, поэтому было бы сложнее предоставить неправильные аргументы функции - Аналогично, некоторые недопустимые операции могут вызвать ошибки компиляции, такие как добавление a
GPSCoordinate
кZoomLevel
другому или другому.GPSCoordinate
- Легальные операции будут легко представлять и безопасны. т.е. вычитание двух
GPSCoordinate
s дастGPSDisplacement
Однако большая часть кода C ++, который я видел, использует много примитивных типов, и я полагаю, что для этого должна быть веская причина. Это хорошая идея использовать объекты для чего-либо, или у него есть недостатки, о которых я не знаю?
Ответы:
Да, безусловно. Функции / методы, которые принимают слишком много аргументов, являются запахом кода и указывают по крайней мере на одно из следующего:
Если это последний случай (и ваш пример, безусловно, так), самое время сделать какую-нибудь из этих «абстрактных штанов » , о которых крутые парни говорили о, всего несколько десятилетий назад. На самом деле, я бы пошел дальше вашего примера и сделал бы что-то вроде:
(Я не знаком с GPS, так что это, вероятно, неправильная абстракция; например, я не понимаю, как зум имеет отношение к вызываемой функции
getGPS
.)Пожалуйста, предпочитайте классы как
PixelCoordinate
болееstd::pair<T, T>
, даже если у обоих одинаковые данные. Это добавляет семантическое значение, плюс немного дополнительной безопасности типов (компилятор не даст вам передатьScreenCoordinate
a,PixelCoordinate
даже если обаpair
s.источник
Отказ от ответственности: я предпочитаю C на C ++
Вместо классов я бы использовал структуры. Этот пункт в лучшем случае педантичен, так как структуры и классы почти идентичны (см. Здесь для простой диаграммы или этот вопрос SO ), но я думаю, что есть разница в дифференциации.
Программисты на Си знакомы с структурами как блоками данных, которые при группировании имеют большее значение, тогда как когда я вижу классы, я предполагаю, что есть некоторое внутреннее состояние и методы для манипулирования этим состоянием. Координата GPS не имеет состояния, только данные.
Из вашего вопроса, кажется, это именно то, что вы ищете, общий контейнер, а не структура с состоянием. Как уже упоминали другие, я бы не стал помещать Zoom в собственный класс; Лично я ненавижу классы, созданные для хранения типов данных (мои профессора любят создавать
Height
иId
классы).Я не думаю, что это проблема. Если вы сократите количество параметров, сгруппировав очевидные вещи, как вы сделали, заголовков должно быть достаточно, чтобы предотвратить эти проблемы. Если вы действительно беспокоитесь, разделите примитивы структурой, которая должна дать вам ошибки компиляции для распространенных ошибок. Легко поменять местами два параметра, но сложнее, если они будут дальше друг от друга.
Хорошее использование перегрузок операторов.
Также хорошо использовать операторские перегрузки.
Я думаю, что вы на правильном пути. Еще одна вещь, которую стоит рассмотреть, это то, как это вписывается в остальную часть программы.
источник
Преимущество использования выделенных типов состоит в том, что намного сложнее исказить порядок аргументов. Например, первая версия вашей примерной функции начинается с цепочки из 9 двойных чисел. Когда кто-то использует эту функцию, очень легко испортить порядок и передать углы карданного подвеса вместо координат GPS и наоборот. Это может привести к очень странным и трудно отслеживаемым ошибкам. Когда вместо этого вы передаете только три аргумента с разными типами, эта ошибка может быть перехвачена компилятором.
На самом деле я хотел бы пойти дальше и ввести типы, которые технически идентичны, но имеют другое семантическое значение. Например, имея класс
PlaneAngle3d
и отдельный классGimbalAngle3d
, хотя оба представляют собой набор из трех двойных. Это сделало бы невозможным использование одного в качестве другого, если оно не является преднамеренным.Я бы порекомендовал использовать typedefs, которые являются просто псевдонимами для примитивных типов (
typedef double ZoomLevel
) не только по этой причине, но и по другой: он легко позволяет позже изменить тип. Когда вы однажды поймете, что использованиеfloat
может быть таким же хорошим,double
но может быть более эффективным с точки зрения памяти и процессора, вам просто нужно изменить это в одном месте, а не в десятках раз.источник
Прекрасные ответы здесь уже есть, но я хотел бы добавить одну вещь:
Использование
PixelCoordinate
иPixelSize
класс делает вещи очень специфичными. ГенералCoordinate
илиPoint2D
класс, возможно, лучше подойдет вашим потребностям. APoint2D
должен быть классом, реализующим наиболее распространенные точечные / векторные операции. Вы могли бы реализовать такой класс даже обобщенно (Point2D<T>
где T может бытьint
илиdouble
или что - то еще).Двухточечные / векторные операции настолько распространены для многих задач по программированию 2D-геометрии, что, по моему опыту, такое решение значительно повысит вероятность повторного использования существующих 2D-операций. Вы избежать необходимости повторных их реализации для каждого
PixelCoordinate
,GPSCoordinate
«Whatsover» -координата класса снова и снова.источник
struct PixelCoordinate:Point2D<int>
если хотите правильную безопасность типовЭто [более читабельно]. Это тоже хорошая идея.
Но имеет ли смысл создавать классы PixelCoordinate и PixelSize?
Если они представляют разные понятия, это имеет смысл (то есть «да»).
Вы могли бы начать делать
typedef std::pair<int,int> PixelCoordinate, PixelSize;
. Таким образом, вы покрываете семантику (вы работаете с координатами и размерами пикселей, а не с std :: pair в вашем клиентском коде), и если вам нужно расширить, вы просто замените typedef классом, и ваш клиентский код должен требуют минимальных изменений.Вы могли бы также определить его, но я буду использовать,
double
пока мне не понадобится связанная с ним функциональность, которая не существует для double (например, наличиеZoomLevel::default
значения потребует, чтобы вы определили класс, но пока у вас не будет ничего подобного что, держать его в два раза).Это веские аргументы (вы всегда должны стремиться к тому, чтобы ваши интерфейсы были простыми в использовании и неправильными).
Есть причины; хотя во многих случаях эти причины не являются хорошими. Одной из веских причин для этого может быть производительность, но это означает, что вы должны рассматривать этот вид кода как исключение, а не как правило.
Как правило, рекомендуется убедиться, что если ваши значения всегда отображаются вместе (и не имеют смысла отдельно), вы должны сгруппировать их (по тем же причинам, которые вы указали в своем посте).
То есть, если у вас есть координаты, которые всегда имеют x, y и z, то гораздо лучше иметь
coordinate
класс, чем передавать три параметра везде.источник