Две структуры с одинаковыми членами, но разными именами, это хорошая идея?

49

Я пишу программу, которая включает в себя работу с полярными и декартовыми координатами.

Имеет ли смысл создавать две разные структуры для каждого вида точек, одна с Xи Yчленами, а другая с Rи Thetaчленами.

Или это слишком много, и лучше иметь только одну структуру с членами firstи в secondкачестве членов.

То, что я пишу, просто и не сильно изменится. Но мне любопытно, что лучше с точки зрения дизайна.

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

Моха всемогущий верблюд
источник
11
Я всегда создаю новую структуру / класс для каждой цели. Нужен 3d вектор, создайте struct 3d_vector с тремя поплавками. Нужно представление uvw, создайте struct texture_coords с тремя поплавками. Нужна позиция в трехмерной среде, создайте структурную позицию с тремя поплавками. Вы поняли. Это обеспечивает лучшую читаемость, чем использование одного и того же. Если вам надоело определять одно и то же несколько раз, используйте базовую 3-float-struct и определите несколько имен в качестве одной и той же структуры.
Кевин
Если у них есть общие методы, то, возможно, один. Вам когда-нибудь нужно сравнить равенство двух?
Папарацци
8
@ Сидни Если вам абсолютно не нужна функциональность, я бы не стал. Вам нужно выполнить операцию sin / arcsin для преобразования двух представлений. Это будет вводить бит бит в младшие биты каждый раз, когда вы делаете преобразование. Я почти уверен, что вы столкнетесь с такой же болью, с которой я столкнулся, пытаясь разобраться с классом, который предоставлял как частоту событий, так и время между событиями (x и 1 / x) для представления чего-либо. Отслеживать, какое представление является каноническим в классе, и справляться со всеми головными болями округления - это не то, что я хотел бы сделать снова.
Дэн Нили
3
Хорошим примером типа данных, который может представлять множество вещей, является строка, но «stringy typed» является антипаттерном. По твоему примеру. Попробуйте реализовать скалярное произведение для типа, который поддерживает обе системы координат.
Натан Купер
1
Вы должны стремиться использовать разные типы, когда это применимо, чтобы получить преимущества безопасности типов - это предотвратит отправку неправильного типа в / из функции (используя проверку правильности времени компиляции, в зависимости от вашего языка программирования). Это облегчит обслуживание, потому что вы можете найти все истинные применения какого-либо типа (без ошибочного использования из-за смешения типов).
Эрик Эйдт

Ответы:

17

Я видел оба решения, поэтому оно определенно зависит от контекста.

Для удобства чтения наличие нескольких структур, как вы предлагаете, очень эффективно. Однако в некоторых средах вы хотите выполнить общие манипуляции с этими структурами, и вы обнаружите, что дублируете код, такой как матричные * векторные операции. Это может расстраивать, когда конкретная операция недоступна в вашем виде вектора, потому что никто не перенес ее туда.

Чрезвычайное решение (которое мы в конечном итоге дали) состоит в том, чтобы иметь шаблонный базовый класс CRTP с функциями get <0> () get <1> () и get <2> () для получения элементов в общем виде. Эти функции затем определяются в декартовой или полярной структуре, которая является производной от этого базового класса. Это решает все проблемы, но приходит по довольно глупой цене: нужно изучать метапрограммирование шаблонов. Однако, если шаблонное метапрограммирование уже является честной игрой для вашего проекта, это может быть хорошим совпадением.

Корт Аммон
источник
1
Ваш ответ очень интересный. Можно ли привести пример?
Моха всемогущий верблюд
1
Я могу привести пример того, что я сделал: FIR-фильтрация поляров, декартов и их векторов. Математика была довольно схожей: без угла (без), код все равно дублировал некоторые части по соображениям производительности, и мы использовали шаблоны там, где это было одинаково. Использовал разные имена для всего материала. «Крайнее решение» Корта могло бы спасти несколько дубликатов, но не почти все.
Евгений Рябцев
1
Моя первая реакция состояла в том, что подобную ситуацию лучше разрешить путем кастинга, но это оказывается несколько рискованным .
200_success 11.11.15
114

Да, это имеет большой смысл.

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

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

Килиан Фот
источник
6
+1 Прекрасное описание использования компьютера, чтобы делать то, что хорошо умеют делать компьютеры, и позволить вашему мозгу сосредоточиться на своей работе.
BrianH
8
«Программы должны быть написаны для того, чтобы люди могли их читать, и только для машин». - из «Структуры и интерпретации компьютерных программ» Абельсона и Суссмана.
Хловдал
18

Да, хотя и декартовы, и полярные являются (на их месте) чрезвычайно разумными схемами представления координат, в идеале они никогда не должны смешиваться (если у вас есть точка декартова {1,1}, это очень отличная точка от полярной {1,1). }).

В зависимости от ваших потребностей, она также может быть стоит реализации координатно - интерфейс, с помощью методов , таких как X(), Y(), Displacement()и Angle()(или , возможно , Radius()и Theta(), в зависимости от).

Vatine
источник
Еще более важно, если ОП создает классы из этих структур, поскольку операции над декартовыми и полярными координатами различны.
Mindwin
1
+1 за последний абзац, это идеальное решение. Точка - это пространство, это объект; внутреннее представление этой точки не должно иметь значения. Конечно, реальные проблемы (производительность, ошибки округления) могут стать причиной этого. Все зависит от того, для чего это используется.
BlueRaja - Дэнни Пфлюгофт
А также, для этого примера, это вряд ли изменится, но если бы они были 2 другими классами, ничто не говорит вам, что они могут расходиться в некоторых точках.
красители
8

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

Если вы просто создаете одну структуру с именами членов firstи second, то имена ничего не значат; вы бы по существу относились к ним как к адресам памяти. Это противоречит цели языка программирования высокого уровня.

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

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

200_success
источник
Лучший ответ, ИМХО
Дин Рэдклифф
2

Во-первых, имейте оба явно, согласно полностью обоснованному ответу @ Kilian-foth.

Тем не менее, я хотел бы добавить:

Спросите: действительно ли у вас есть операции, которые являются общими для обоих, если рассматривать их как пары double? Обратите внимание, это не то же самое, что сказать, что у вас есть операции, которые применяются к обоим в их собственных терминах. Например, «plot (Coord)» заботится о том, Coordявляется ли полярным или декартовым. С другой стороны, сохранение файла просто обрабатывает данные как есть. Если у вас действительно есть общие операции, рассматривает как определение базового класса, или определения преобразователя к std::pair<double, double>или tupleили что - то у вас есть на вашем языке.

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

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

Кит
источник
1

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

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

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

Svalorzen
источник
Имена членов в обоих классах не совпадают. на самом деле имена - единственная разница между двумя классами
Моха всемогущий верблюд
@ Mhd.Tahawi Вы можете реализовать методы получения и установки с соответствующими именами, гарантируя, что класс, который вы используете, одинаковы, но предоставляя соответствующие имена для операций, которые вы хотите использовать. Это становится немного более многословным, но вам придется дублировать меньше кода.
Svalorzen
0

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

Представьте себе сценарий: у вас есть пара декартовых точек: pntA и pntB. Затем вы решаете, по какой-то причине, что они должны быть лучше представлены в полярных координатах, и меняете объявление и конструктор.

Теперь, если все ваши операции были просто вызовами методов, такими как:

double distance = pntA.distanceFrom(pntB);

тогда ты в порядке. Но что, если вы использовали членов явно? сравнить

double leftMargin = abs(pntA.x - pntB.x);
double leftMargin = abs(pntA.first - pntB.first);

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

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

double distance = calculate_distance_polar(cartesianPointA, polarPointB);

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

IMIL
источник