C #: перечисление анти-паттернов - PullRequest
6 голосов
/ 12 октября 2010

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

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

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

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

Или даже код старой школы, подобный этому:

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

Какие еще анти-паттерны и лучшие реализации вы испытали с перечислениями ?

Ответы [ 5 ]

11 голосов
/ 12 октября 2010

Я думаю, что Enums весьма полезны. Я написал несколько расширений для 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 = ',';
}

Это позволит мне определить 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.

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

1 голос
/ 12 июля 2014

Это не ответ, а лишь внесение в список анти-паттернов Enum.

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

Два случая:

  1. До употребления
  2. После употребления

..

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


    public class UIELementType
    {
        public const string FactoryDomain = "FactoryDomain";
        public const string Attributes = "Attributes";
    }
1 голос
/ 12 октября 2010

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

0 голосов
/ 28 сентября 2018

Все зависит от того, что вы пытаетесь сделать с помощью enum.

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

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

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

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

    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, но мне действительно нравятся enum'ы, однако я чувствую, что когда вы пытаетесь отделить логику с помощью перечислений, это СИЛЬНЫЙ анти-паттерн.

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

0 голосов
/ 12 октября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...