дилемма
Я читал много лучших практических книг по объектно-ориентированным практикам, и почти в каждой прочитанной мной книге была часть, в которой говорится, что перечисления - это запах кода. Я думаю, что они пропустили ту часть, где они объясняют, когда перечисления действительны.
Поэтому я ищу руководящие принципы и / или варианты использования, в которых перечисления НЕ являются запахом кода и фактически являются допустимой конструкцией.
Источники:
«ПРЕДУПРЕЖДЕНИЕ. Как правило, перечисления являются запахами кода и должны быть преобразованы в полиморфные классы. [8]» Seemann, Mark, Dependency Injection, .Net, 2011, p. 342
[8] Мартин Фаулер и др., Рефакторинг: Улучшение дизайна существующего кода (Нью-Йорк: Аддисон-Уэсли, 1999), 82.
контекст
Причиной моей дилеммы является торговый API. Они дают мне поток данных о тиках, отправляя этот метод:
void TickPrice(TickType tickType, double value)
где enum TickType { BuyPrice, BuyQuantity, LastPrice, LastQuantity, ... }
Я попытался обернуть вокруг этого API, потому что ломать изменения - образ жизни для этого API. Я хотел отслеживать значение каждого последнего полученного типа тика в моей оболочке, и я сделал это с помощью словаря тиктипов:
Dictionary<TickType,double> LastValues
Мне это показалось правильным использованием перечисления, если они используются в качестве ключей. Но у меня возникают другие мысли, потому что у меня есть место, где я принимаю решение на основе этой коллекции, и я не могу придумать, как бы я мог исключить оператор switch, я мог бы использовать фабрику, но эта фабрика все еще будет иметь переключить заявление где-нибудь. Мне казалось, что я просто перемещаю вещи, но все еще пахнет.
Легко найти DON'Ts перечислений, но не так просто, и я был бы признателен, если бы люди могли поделиться своим опытом, плюсами и минусами.
Второстепенные мысли
Некоторые решения и действия основаны на них, TickType
и я не могу придумать способ устранить операторы enum / switch. Самым чистым решением, которое я могу придумать, является использование фабрики и возвращение реализации на основе TickType
. Даже тогда у меня все еще будет оператор switch, который возвращает реализацию интерфейса.
Ниже приведен один из примеров классов, где у меня есть сомнения, что я могу использовать неправильное перечисление:
public class ExecutionSimulator
{
Dictionary<TickType, double> LastReceived;
void ProcessTick(TickType tickType, double value)
{
//Store Last Received TickType value
LastReceived[tickType] = value;
//Perform Order matching only on specific TickTypes
switch(tickType)
{
case BidPrice:
case BidSize:
MatchSellOrders();
break;
case AskPrice:
case AskSize:
MatchBuyOrders();
break;
}
}
}
enums as switch statements might be a code smell ...
Ответы:
Перечисления предназначены для случаев использования, когда вы буквально перечислили все возможные значения, которые может принимать переменная. Когда-либо. Представьте себе варианты использования, например дни недели или месяцы года, или значения конфигурации аппаратного регистра. Вещи, которые в высокой степени стабильны и представимы простым значением.
Имейте в виду, что если вы создаете антикоррупционный слой, вы не можете избежать использования инструкции switch где-то из-за дизайна, который вы упаковываете, но если вы все сделаете правильно, вы можете ограничить его этим одним местом и использовать полиморфизм в другом месте.
источник
Во-первых, запах кода не означает, что что-то не так. Это означает, что что-то может быть не так.
enum
пахнет, потому что его часто злоупотребляют, но это не значит, что вы должны избегать их. Просто вы обнаружите, что печатаетеenum
, останавливаетесь и проверяете лучшие решения.Частный случай, который случается чаще всего, - это когда разные перечисления соответствуют разным типам с разным поведением, но одинаковым интерфейсом. Например, общение с разными бэкэндами, рендеринг разных страниц и т. Д. Они гораздо естественнее реализованы с использованием полиморфных классов.
В вашем случае TickType не соответствует разным поведениям. Это разные типы событий или разные свойства текущего состояния. Поэтому я думаю, что это идеальное место для перечисления.
источник
При передаче данных перечисления не имеют запаха кода
ИМХО, при передаче данных использовать
enums
для указания того, что поле может иметь значение из ограниченного (редко меняющегося) набора значений, хорошо.Считаю предпочтительным передачу произвольной
strings
илиints
. Строки могут вызывать проблемы из-за изменений в написании и заглавных буквах. Ints позволяют передавать из диапазона значений и имеют мало семантики (например , я получаю3
от вашего торгового обслуживания, что же это значит?LastPrice
?LastQuantity
? Что - то еще?Передача объектов и использование иерархий классов не всегда возможны; например, wcf не позволяет принимающей стороне различать, какой класс был отправлен.
Другая причина, по которой не нужно передавать объекты класса, заключается в том, что для службы может потребоваться совершенно другое поведение для переданного объекта (такого как
LastPrice
), чем для клиента. В этом случае отправка класса и его методов нежелательна.Являются ли заявления о переключении плохими?
ИМХО, единственное
switch
утверждение, которое вызывает различные конструкторы в зависимости от,enum
не является запахом кода. Это не обязательно лучше или хуже, чем другие методы, такие как рефлексия на имя типа; это зависит от реальной ситуации.Включение
enum
повсюду - это запах кода, oop предлагает альтернативы, которые зачастую лучше:источник
int
. Моя обертка - это та, которая использует,TickType
отлитая из полученногоint
. Несколько событий используют этот тип тика с дикими переменными сигнатурами, которые являются ответами на различные запросы. Является ли обычной практикой постоянное использованиеint
различных функций? Например,TickPrice(int type, double value)
использует 1,3 и 6, вtype
то время какTickSize(int type, double value
использует 2,4, и 5? Имеет ли смысл разделять их на два события?Что делать, если вы пошли с более сложным типом:
тогда вы можете загрузить свои типы из рефлексии или построить его самостоятельно, но главное, что вы здесь делаете, это то, что вы придерживаетесь принципа Открытого Закрытия SOLID
источник
TL; DR
Чтобы ответить на вопрос, мне сейчас трудно думать, что перечисления времени не являются запахом кода на каком-то уровне. Есть определенное намерение, которое они декларируют эффективно (существует явно ограниченное, ограниченное количество возможностей для этой ценности), но их внутренне замкнутый характер делает их архитектурно неполноценными.
Прошу прощения, как я рефакторинг тонн моего старого кода. / вздох; ^ D
Но почему?
Довольно хороший обзор, почему это так от LosTechies здесь :
Еще один случай реализации ...
Суть в том, что если у вас есть поведение, которое зависит от значений перечисления, почему бы вместо этого не иметь различные реализации аналогичного интерфейса или родительского класса, который гарантирует, что значение существует? В моем случае я смотрю различные сообщения об ошибках, основанные на кодах состояния REST. Вместо того...
... возможно, я должен обернуть коды статуса в классах.
Затем каждый раз, когда мне нужен новый тип IAmStatusResult, я его кодирую ...
... и теперь я могу убедиться, что более ранний код понимает, что он имеет
IAmStatusResult
область действия, и ссылаться на него,entity.ErrorKey
а не на более замысловатый deadend_getErrorCode(403)
.И, что более важно, каждый раз , когда я добавляю новый тип возвращаемого значения, не нужно добавлять никакого другого кода для его обработки . Хоть знаешь, то
enum
иswitch
были кодовые запахи.Прибыль.
источник
StatusResult
значение? Вы могли бы утверждать, что перечисление полезно в качестве прецедента, запоминающегося человеком, в этом случае использования, но я, вероятно, все еще назвал бы это запахом кода, поскольку есть хорошие альтернативы, которые не требуют закрытой коллекции.enum
, вы действительно должны кода обновления каждый раз , когда вы добавляете или удаляете значение, и потенциально много мест, в зависимости от того, как структурирована вашего кода. Использование полиморфизма вместо перечисления - это то же самое, что гарантировать, что ваш код находится в нормальной форме Бойса-Кодда .Использование перечисления является запахом кода или нет, зависит от контекста. Я думаю, что вы можете получить некоторые идеи для ответа на ваш вопрос, если вы рассматриваете проблему выражения . Итак, у вас есть коллекция различных типов и набор операций над ними, и вам нужно организовать свой код. Есть два простых варианта:
Какое решение лучше?
Если, как указал Карл Билефельдт, ваши типы фиксированы, и вы ожидаете, что система будет расти в основном за счет добавления новых операций к этим типам, тогда лучше использовать enum и оператор switch: каждый раз, когда вам нужна новая операция, вы просто реализуйте новую процедуру, тогда как при использовании классов вам придется добавить метод к каждому классу.
С другой стороны, если вы ожидаете довольно стабильный набор операций, но считаете, что вам придется со временем добавлять больше типов данных, использование объектно-ориентированного решения более удобно: поскольку новые типы данных должны быть реализованы, вы просто продолжайте добавлять новые классы, реализующие тот же интерфейс, тогда как если бы вы использовали перечисление, вам пришлось бы обновлять все операторы switch во всех процедурах, использующих перечисление.
Если вы не можете классифицировать свою проблему по одному из двух приведенных выше вариантов, вы можете посмотреть на более сложные решения (см., Например, снова приведенную выше страницу Википедии для краткого обсуждения и некоторой ссылки для дальнейшего чтения).
Поэтому вы должны попытаться понять, в каком направлении может развиваться ваше приложение, а затем выбрать подходящее решение.
Поскольку книги, на которые вы ссылаетесь, имеют дело с объектно-ориентированной парадигмой, неудивительно, что они склонны использовать перечисления. Тем не менее, объектно-ориентированное решение не всегда лучший вариант.
Итог: перечисления не обязательно являются запахом кода.
источник
paint()
,show()
,close()
resize()
операции и пользовательские виджеты) , то объектно-ориентированный подход лучше в том смысле , что позволяет добавить новый тип , не затрагивая слишком много существующего кода (вы в основном реализуете новый класс, который является локальным изменением).