Рассмотрим пример ниже. Любое изменение в перечислении ColorChoice влияет на все подклассы IWindowColor.
Имеют ли перечисления тенденцию вызывать хрупкие интерфейсы? Есть ли что-то лучше, чем enum для большей полиморфной гибкости?
enum class ColorChoice
{
Blue = 0,
Red = 1
};
class IWindowColor
{
public:
ColorChoice getColor() const=0;
void setColor( const ColorChoice value )=0;
};
Изменить: извините за использование цвета в качестве моего примера, это не то, о чем вопрос. Вот другой пример, который избегает красную сельдь и предоставляет больше информации о том, что я подразумеваю под гибкостью.
enum class CharacterType
{
orc = 0,
elf = 1
};
class ISomethingThatNeedsToKnowWhatTypeOfCharacter
{
public:
CharacterType getCharacterType() const;
void setCharacterType( const CharacterType value );
};
Кроме того, представьте, что дескрипторы для соответствующего подкласса ISomethingThatNeedsToKnowWhatTypeOfCharacter разрабатываются с помощью фабричного шаблона проектирования. Теперь у меня есть API, который не может быть расширен в будущем для другого приложения, где допустимые типы символов: {человек, карлик}.
Изменить: Просто чтобы быть более конкретным о том, над чем я работаю. Я проектирую сильную привязку этой спецификации ( MusicXML ) и использую классы enum для представления тех типов в спецификации, которые объявлены с помощью xs: enumeration. Я пытаюсь думать о том, что произойдет, когда выйдет следующая версия (4.0). Может ли моя библиотека классов работать в режиме 3.0 и в режиме 4.0? Если следующая версия на 100% обратно совместима, то возможно. Но если значения перечисления были удалены из спецификации, то я мертв в воде.
источник
Ответы:
При правильном использовании перечисления гораздо более читабельны и надежны, чем «магические числа», которые они заменяют. Обычно я не вижу, чтобы они делали код более хрупким. Например:
value
является ли допустимое значение цвета или нет. Компилятор уже сделал это.enum class
функция в современном C ++ даже позволяет вам заставить людей всегда писать первое вместо второго.Однако использование enum для цвета сомнительно, потому что во многих (большинстве?) Ситуациях нет причин ограничивать пользователя таким небольшим набором цветов; Вы можете также позволить им передавать любые произвольные значения RGB. В проектах, с которыми я работаю, небольшой список цветов, подобных этому, может появиться только как часть набора «тем» или «стилей», которые должны выступать в качестве тонкой абстракции над конкретными цветами.
Я не уверен, к чему твой вопрос о "полиморфной гибкости". У перечислений нет исполняемого кода, поэтому нечего делать полиморфным. Возможно, вы ищете шаблон команды ?
Edit: Post-edit, мне все еще не ясно, какую именно расширяемость вы ищете, но я все еще думаю, что шаблон команды - это самая близкая вещь, которую вы получите к «полиморфному перечислению».
источник
Нет, это не так. Есть два случая:
хранить, возвращать и пересылать значения перечисления, никогда не воздействуя на них, и в этом случае на них не влияют изменения в перечислении, или
работать с отдельными значениями перечисления, и в этом случае любое изменение в перечислении, конечно, естественно, неизбежно, обязательно , должно быть учтено с соответствующим изменением логики реализатора.
Если вы поместите «Сфера», «Прямоугольник» и «Пирамида» в перечисление «Форма» и передадите такое перечисление какой-то
drawSolid()
функции, которую вы написали для рисования соответствующего тела, а затем однажды утром вы решите добавить « Ikositetrahedron "значение для перечисления, вы не можете ожидать, чтоdrawSolid()
функция останется неизменной; Если вы ожидали, что он каким-то образом будет рисовать икоситетраэдры, без необходимости сначала писать реальный код для рисования икоситетраэдров, то это ваша ошибка, а не ошибка перечисления. Так:Нет, они не Хрупкие интерфейсы вызывают программисты, которые считают себя ниндзя и пытаются скомпилировать свой код без достаточного количества предупреждений. Затем компилятор не предупреждает их о том, что их
drawSolid()
функция содержитswitch
оператор, в котором отсутствуетcase
предложение для вновь добавленного значения перечисления «Ikositetrahedron».Способ, которым он должен работать, аналогичен добавлению нового чисто виртуального метода в базовый класс: тогда вы должны реализовать этот метод для каждого отдельного наследника, иначе проект не будет и не должен собираться.
Теперь, по правде говоря, перечисления не являются объектно-ориентированной конструкцией. Это скорее прагматический компромисс между объектно-ориентированной парадигмой и структурной парадигмой программирования.
Чисто объектно-ориентированный способ делать вещи - не иметь перечислений вообще, а вместо этого иметь объекты целиком.
Итак, чисто объектно-ориентированный способ реализации примера с использованием твердых тел, конечно же, заключается в использовании полиморфизма: вместо того, чтобы иметь один чудовищный централизованный метод, который знает, как рисовать все, и нужно сказать, какое тело рисовать, вы объявляете " Сплошной »класс с абстрактным (чисто виртуальным)
draw()
методом, а затем вы добавляете подклассы« Сфера »,« Прямоугольник »и« Пирамида », каждый из которых имеет свою собственную реализацию,draw()
которая умеет рисовать себя.Таким образом, когда вы вводите подкласс «Ikositetrahedron», вам просто нужно предоставить
draw()
для него функцию, и компилятор напомнит вам сделать это, не позволяя создавать экземпляр «Icositetrahedron» в противном случае.источник
default:
предложения должны это сделать. Быстрый поиск по теме не дал более определенных результатов, так что это могло бы подойти как предмет другогоprogrammers SE
вопроса.Pyramid
самом деле знает, какdraw()
пирамиду. В лучшем случае он может быть производнымSolid
и иметьGetTriangles()
метод, и вы можете передать егоSolidDrawer
службе. Я думал, что мы уходим от примеров физических объектов как примеров объектов в ООП.Перечисления не создают хрупкие интерфейсы. Злоупотребление перечислениями делает.
Для чего нужны перечисления?
Перечисления предназначены для использования в качестве наборов осмысленно именованных констант. Они должны использоваться, когда:
Хорошее использование перечислений:
System.DayOfWeek
) Если вы не имеете дело с каким-то невероятно малоизвестным календарем, будет только 7 дней недели.System.ConsoleColor
). Некоторые могут не согласиться с этим, но .Net решил сделать это по причине. В консольной системе .Net для консоли доступно только 16 цветов. Эти 16 цветов соответствуют устаревшей цветовой палитре, известной как CGA или «Цветной графический адаптер» . Новые значения никогда не будут добавлены, что означает, что это действительно разумное применение перечисления.Thread.State
) Разработчики Java решили, что в модели потоков Java всегда будет только фиксированный набор состояний, в которыхThread
может находиться a , и поэтому для упрощения эти различные состояния представлены в виде перечислений , Это означает, что многие проверки на основе состояний являются простыми if и переключателями, которые на практике работают с целочисленными значениями, и программисту не нужно беспокоиться о том, какие значения есть на самом деле.System.Text.RegularExpressions.RegexOptions
) Битовые флаги являются очень распространенным использованием перечислений. Фактически настолько распространенный, что в .Net все перечисления имеютHasFlag(Enum flag)
встроенный метод. Они также поддерживают побитовые операторы, и естьFlagsAttribute
возможность пометить перечисление как предназначенное для использования в качестве набора битовых флагов. Используя перечисление в качестве набора флагов, вы можете представлять группу логических значений в одном значении, а также иметь флаги с четкими именами для удобства. Это было бы чрезвычайно полезно для представления флагов регистра состояния в эмуляторе или для представления прав доступа к файлу (чтение, запись, выполнение) или практически для любой ситуации, когда набор связанных опций не является взаимоисключающим.Неправильное использование перечислений:
True
подразумевается отсутствие флагаFalse
). Это поведение может быть дополнительно поддержано с помощью специализированных функций (то естьIsTrue(flags)
иIsFalse(flags)
).источник
RegexOptions
MSND.Microsoft.com/en-us/library/Перечисления являются большим улучшением по сравнению с магическими идентификационными номерами для закрытых наборов значений, которые не имеют большой функциональности, связанной с ними. Обычно вас не волнует, какое число на самом деле связано с перечислением; в этом случае его легко расширить, добавив новые записи в конце, хрупкости не должно получиться.
Проблема в том, что у вас есть значительная функциональность, связанная с перечислением. То есть у вас есть такой код:
Один или два из них в порядке, но как только у вас есть такое осмысленное перечисление, эти
switch
утверждения, как правило, размножаются как трибулы. Теперь каждый раз, когда вы расширяете enum, вам нужно выследить все связанные с нимиswitch
es, чтобы убедиться, что вы охватили все. Это хрупко Источники, такие как Чистый код , предполагают, что вы должны иметь не более одногоswitch
на каждое перечисление.Вместо этого в этом случае следует использовать принципы ОО и создать интерфейс для этого типа. Вы можете по-прежнему сохранять перечисление для связи с этим типом, но как только вам нужно что-то с ним сделать, вы создаете объект, связанный с перечислением, возможно, с использованием Factory. Это гораздо проще в обслуживании, так как вам нужно искать только одно место для обновления: ваш Factory и добавляя новый класс.
источник
Если вы будете осторожны в их использовании, я не буду считать перечисления вредными. Но есть несколько вещей, которые следует учитывать, если вы хотите использовать их в некотором библиотечном коде, в отличие от одного приложения.
Никогда не удаляйте и не переупорядочивайте значения. Если в какой-то момент у вас есть значение перечисления в вашем списке, это значение должно быть связано с этим именем на всю вечность. Если вы хотите, вы можете в какой-то момент переименовать значения во
deprecated_orc
что угодно, но, не удаляя их, вы можете легче поддерживать совместимость со старым кодом, скомпилированным со старым набором перечислений. Если какой-то новый код не может работать со старыми константами enum, либо сгенерируйте подходящую ошибку там, либо убедитесь, что такое значение не достигает этого куска кода.Не делайте арифметику с ними. В частности, не делайте сравнения заказов. Почему? Потому что тогда вы можете столкнуться с ситуацией, когда вы не можете сохранить существующие значения и одновременно сохранить разумный порядок. Возьмем, например, перечисление для направлений компаса: N = 0, NE = 1, E = 2, SE = 3,… Теперь, если после некоторого обновления вы добавите NNE и так далее между существующими направлениями, вы можете добавить их в конец списка и, таким образом, нарушить порядок, или вы чередуете их с существующими ключами и таким образом нарушаете установленные отображения, используемые в унаследованном коде. Или вы отказываетесь от всех старых ключей и получаете совершенно новый набор ключей, а также некоторый код совместимости, который преобразует старый и новый ключи ради унаследованного кода.
Выберите достаточный размер. По умолчанию компилятор будет использовать наименьшее целое число, которое может содержать все значения перечисления. Это означает, что если при некотором обновлении ваш набор возможных перечислений расширяется с 254 до 259, вам вдруг потребуются 2 байта для каждого значения перечисления вместо одного. Это может повредить структуру и макеты классов повсюду, поэтому постарайтесь избежать этого, используя достаточный размер в первом проекте. C ++ 11 дает вам большой контроль здесь, но в противном случае указание записи также
LAST_SPECIES_VALUE=65535
должно помочь.Есть центральный реестр. Поскольку вы хотите исправить соответствие между именами и значениями, плохо, если вы разрешаете сторонним пользователям вашего кода добавлять новые константы. Ни один проект, использующий ваш код, не должен изменять этот заголовок для добавления новых отображений. Вместо этого они должны добавить вас к ошибке. Это означает, что для упомянутого вами примера человека и дварфа перечисления действительно плохо подходят. Там было бы лучше иметь какой-то реестр во время выполнения, где пользователи вашего кода могут вставить строку и получить уникальный номер, красиво завернутый в какой-то непрозрачный тип. «Число» может даже быть указателем на строку, о которой идет речь, это не имеет значения.
Несмотря на то, что мой последний пункт выше делает перечисления плохо подходящими для вашей гипотетической ситуации, ваша реальная ситуация, когда некоторые спецификации могут измениться, и вам придется обновить какой-то код, кажется, вполне вписывается в центральный реестр вашей библиотеки. Поэтому перечисления должны быть уместны, если вы принимаете мои другие предложения близко к сердцу.
источник