C #: Enum Anti-patterns

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

Например, я видел такой код:

switch(enumValue) {
    case myEnum.Value1:
        // ...
        break;
    case myEnum.Value2:
        // ...
        break;
}

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

Или даже код старой школы вроде этого:

if(enumValue == myEnum.Value1) {
   // ...
} else if (enumValue == myEnum.Value2) {
   // ...
}

С какими еще антишаблонами и лучшими реализациями вы сталкивались с перечислениями?


person Seb Nilsson    schedule 12.10.2010    source источник
comment
Словарь лучше Enum ?? можешь объяснить почему?   -  person Oren A    schedule 12.10.2010
comment
Вы все смешиваете вместе. switch против enum само по себе неплохо. иногда это необходимо, например на указанном заводе.   -  person Andrey    schedule 12.10.2010
comment
Вы должны использовать Dictionary с ключом перечисления. Таким образом, вы получите лучшее из обоих миров: строго типизированный словарь И никаких длинных операторов переключения.   -  person VitalyB    schedule 12.10.2010
comment
Приведенный выше код может быть отличной реализацией фабрики, которая взаимодействует с чем-то вне сборки, где модель наследования не существует.   -  person Jon Hanna    schedule 12.10.2010
comment
Как словарь выполняет различные функции в зависимости от значения?   -  person cjk    schedule 12.10.2010
comment
@Oren Не додумался, отредактировал ...   -  person Seb Nilsson    schedule 12.10.2010


Ответы (5)


Я считаю, что перечисления весьма полезны. Я написал несколько расширений для Enum, которые сделали его использование еще более ценным.

Во-первых, есть метод расширения Description

public static class EnumExtensions
{
    public static string Description(this Enum value)
    {
        var entries = value.ToString().Split(ENUM_SEPERATOR_CHARACTER);
        var description = new string[entries.Length];
        for (var i = 0; i < entries.Length; i++)
        {
            var fieldInfo = value.GetType().GetField(entries[i].Trim());
            var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
            description[i] = (attributes.Length > 0) ? attributes[0].Description : entries[i].Trim();
        }
        return String.Join(", ", description);
    }
    private const char ENUM_SEPERATOR_CHARACTER = ',';
}

Это позволит мне определить en enum следующим образом:

 public enum MeasurementUnitType
 {
    [Description("px")]
    Pixels = 0,
    [Description("em")]
    Em = 1,
    [Description("%")]
    Percent = 2,
    [Description("pt")]
    Points = 3
 }

И получите метку, выполнив следующие действия: var myLabel = rectangle.widthunit.Description() (устраняя необходимость в выражении switch).

Это, кстати, вернет «px», если rectangle.widthunit = MeasurementUnitType.Pixels, или «px, em», если rectangle.widthunit = MeasurementUnitType.Pixels | MeasurementUnitType.Em.

Тогда есть

    public static IEnumerable<int> GetIntBasedEnumMembers(Type @enum)
    {
        foreach (FieldInfo fi in @enum.GetFields(BindingFlags.Public | BindingFlags.Static))
            yield return (int)fi.GetRawConstantValue();
    }

Это позволит мне пройти любое перечисление со значениями на основе int и вернуть сами значения int.

Я считаю, что они очень полезны в уже готовой полезной концепции.

person danijels    schedule 12.10.2010
comment
Мы не делаем это с более коротким и простым кодом со словарем ‹TEnum, TDescString›? Какое значение имеет приведенный выше код? - person Seb Nilsson; 12.10.2010
comment
@Seb: Несколько причин: во-первых, описание находится рядом с объявлением, а не где-то еще, если вы используете словарь. Во-вторых, описание всегда присутствует с типом перечисления, что приводит к ... наконец, тип может быть импортирован в другую сборку, а значения перечисления и их описания могут быть отражены и представлены пользователю (полезно для редакторов и иногда Я сделал). - person Skizz; 12.10.2010
comment
Спасибо, Skizz, за то, что сэкономил мне время;) Молодец. - person danijels; 12.10.2010
comment
@Skizz Очень хорошие отзывы. Это все философское. Можно сказать, что другая сборка не знает об атрибуте, но они могут найти словарь. И если они принадлежат друг другу, возможно, enum и lookup-метод должны быть объединены общим классом. Но опять же, все в порядке, спасибо. - person Seb Nilsson; 12.10.2010
comment
Общий способ здесь - person nawfal; 09.06.2013

Это не ответ, а скорее внесение в список антипаттернов Enum.

Во время проверки кода сегодня утром я столкнулся со случаем, похожим на следующий, все в одном классе.

Два случая:

  1. Перед питьем
  2. После питья

..

    public enum ListEnum 
    { 
        CategoryOne,
        CategoryTwo,
        CategoryThree,
        CategoryFour
    }


    public class UIELementType
    {
        public const string FactoryDomain = "FactoryDomain";
        public const string Attributes = "Attributes";
    }
person ElHaix    schedule 12.07.2014

Все зависит от того, что вы пытаетесь сделать с перечислением.

  1. Если вы пытаетесь помешать вашим разработчикам передавать магические числа в свои операции и хотите сохранить целостность данных в своей БД, ДА! Используйте T4-шаблоны (используя ORM), чтобы перейти к таблице MeasurementUnitTypes и сгенерировать перечисление со столбцами ID, Name и Description, соответствующими enum 'int, Enum_Name и Description Attribute (хороший подход для дополнительных полей \ данных для перечисления @danijels) как было предложено выше. Если вы добавляете новый тип измерения в таблицу MeasurementUnitTypes, вы можете просто щелкнуть правой кнопкой мыши и запустить T4-Template, и код перечисления будет сгенерирован для этой новой строки, добавленной в таблицу. Мне не нравятся жестко закодированные данные в моем приложении, которые не связаны с моей БД, поэтому упоминается подход T4-Template. В противном случае он не может быть расширен ... что, если какая-то другая внешняя система хочет получить наши критерии измерения, используемые в нашей системе, тогда он жестко запрограммирован в системе, и вы не можете предоставить его клиенту через службу. Это осталось там.

  2. Если цель не связана с данными и у вас есть логика, назначенная конкретному перечислению, тогда НЕТ! это нарушает SOLID (принцип открытого закрытия), как если бы вы где-то в своем приложении применили переключатель или группу Ifs для действия логики для каждого перечисления, ТАКЖЕ, если вы сделали это ДЕЙСТВИТЕЛЬНО плохо, эти переключатели или Ifs повсюду .... удачи в добавлении нового перечисления ... чтобы оно не было открыто для расширения и закрыто для модификации, поскольку вам необходимо изменить существующий код в соответствии с принципом SOLID.

    Если ваш выбор - 2, я предлагаю заменить ваше перечисление следующим, используя пример из комментария @danijels:

    public interface IMeasurementUnitType
    {
        int ID { get; }
    
        string Description { get; }
    
        // Just added to simulate a action needed in the system
        string GetPrintMessage(int size);
    }
    

Приведенный выше код определяет интерфейс (контракт кода), которому должно соответствовать каждое измерение. Теперь давайте определим измерения в процентах и ​​пикселях:

    public class PixelsMeasurementUnitType : IMeasurementUnitType
    {
        public int ID => 1;

        public string Description => "Pixel";

        public string GetPrintMessage(int size)
        {
            return $"This is a {Description} Measurement that is equal to {size} pixels of the total screen size";
        }
    }

    public class PercentMeasurementUnitType : IMeasurementUnitType
    {
        public int ID => 2;

        public string Description => "Persentage";

        public string GetPrintMessage(int size)
        {
            return $"This is a {Description} Measurement that is equal to {size} persent of total screen size (100)";
        }
    }

Итак, мы определили два типа, мы будем использовать их в коде следующим образом:

    var listOfMeasurmentTypes = AppDomain.CurrentDomain.GetAssemblies()
                        .SelectMany(s => s.GetTypes())
                        .Where(p => typeof(IMeasurementUnitType).IsAssignableFrom(p) 
                                    && !p.IsInterface)
                        .ToList();

Здесь мы берем все ТИПЫ, которые расширяют интерфейс IMeasurementUnitType, а НЕ сам интерфейс. Теперь мы можем использовать Activator для создания экземпляров классов для заполнения наших элементов управления пользовательского интерфейса:

    public IEnumerable<IMeasurementUnitType> GetInstantiatedClassesFromTypes(List<Type> types)
    {
        foreach (var type in types)
        {
            yield return (IMeasurementUnitType)Activator.CreateInstance(type);
        }
    }

Вы можете изменить приведенный выше код, чтобы он был универсальным для любого типа, И ТЕПЕРЬ жизнь происходит, и клиент дает новый тип единицы измерения с именем Point в качестве нового требования, мне не нужно МЕНЯТЬ ЛЮБОЙ код, просто добавьте новый тип (продлите код НЕ изменять). Новый тип будет автоматически выбран в приложении.

    public class PointMeasurementUnitType : IMeasurementUnitType
    {
        public int ID => 3;

        public string Description => "Point";

        public string GetPrintMessage(int size)
        {
            return $"This is a {Description} Measurement that is equal to {size} points of total screen size";
        }
    }

Хорошей идеей было бы кэшировать ваши типы для повышения производительности при запуске приложения или попробовать использовать контейнер DI по вашему выбору.

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

Извините за ОГРОМНУЮ SA, но мне очень нравятся перечисления, но я чувствую, что когда вы пытаетесь разделить логику с помощью перечислений, это СИЛЬНЫЙ анти-шаблон.

комментарии приветствуются,

person Ernest Gunning    schedule 28.09.2018

Использование перечислений в не антипаттернах. В некоторых книгах по рефакторингу этот код используется для демонстрации того, как заменить его полиморфизмом. Было бы нормально, если бы вы чрезмерно использовали перечисления в коде.

person Arseny    schedule 12.10.2010
comment
Сказано было не так. Я попросил антишаблоны, включающие перечисления. - person Seb Nilsson; 12.10.2010

Я считаю, что наличие двух операторов переключения является признаком не-объектно-ориентированного дизайна , как описано далее в этом ответе.

person ChrisW    schedule 12.10.2010