C#: Enum антимодели [затворено]

Говореше се, че Enum като цяло нарушава принципите на чистия код, така че търся любимите на хората антимодели на Enum и алтернативни решения за тях.

Например, виждал съм код като този:

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

Това е една стъпка по-добро от switch-операторите с магически низове, но това вероятно би могло да бъде решено по-добре с фабрика, контейнер или друг модел.

Или дори код от старата школа като този:

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

Какви други антишаблони и по-добри реализации сте срещали с enums?


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
Трябва да използвате речник с ключ на enum. По този начин получавате най-доброто от двата свята: Строго въведен речник И без повече дълги команди за превключване.   -  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)


Мисля, че Enums са доста полезни. Написах няколко разширения за Enum, които добавиха още повече стойност към използването му

Първо, има метод за разширение на описанието

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.

Тогава има a

    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: Няколко причини: първо, описанието е до декларацията, а не някъде другаде, ако използвате речник. Второ, описанието винаги присъства с типа enum, което води до... накрая, типът може да бъде импортиран в друга асемблира и стойностите на enum и техните описания могат да бъдат отразени и представени на потребителя (полезно за редактори и е нещо полезно Направих). - person Skizz; 12.10.2010
comment
Благодаря Skizz, че ми спести време ;) Браво. - person danijels; 12.10.2010
comment
@Skizz Много добри точки. Всичко е философско. Може да се каже, че друго събрание няма да знае за атрибута, но може да потърси речник. И ако те принадлежат заедно, може би enum и lookup-method трябва да бъдат обвити от общ клас. Но отново, всички валидни точки, благодаря. - 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

Всичко зависи от това какво се опитвате да направите с enum.

  1. Ако се опитвате да спрете вашите разработчици да предават магически числа във вашите операции и искате да запазите референтната цялост на данните непокътната с вашата DB, тогава ДА! Използвайте T4-Templates (използвайки вашия ORM), за да отидете до вашата таблица MeasurementUnitTypes и да генерирате enum с колони ID, Name и Description, съответстващи на enum' int, Enum_Name и Description Attribute (хубав подход за допълнително поле\данни към enum @danijels) както е предложено по-горе. Ако добавите нов тип измерване към вашата таблица MeasurementUnitTypes, можете просто да щракнете с десния бутон и да изпълните T4-Template и enum кодът се генерира за този нов ред, добавен в таблицата. Не харесвам твърдо кодирани данни в моето приложение, които не се свързват с моята база данни, следователно споменаването на подхода T4-Template. Не е разширяем по друг начин... какво ще стане, ако друга външна система иска да извлече нашите критерии за измерване, използвани в нашата система, тогава тя е твърдо кодирана в системата и не можете да я изложите на клиента чрез услуга. Това остана там.

  2. Ако целта не е свързана с данни и имате някаква логика, присвоена на конкретно преброяване, тогава НЕ! това нарушава SOLID (принцип за отваряне и затваряне), тъй като някъде във вашето приложение бихте приложили превключвател или куп Ifs, за да действате по логиката за преброяване, СЪЩО ТО, ако сте го направили НАИСТИНА лошо, тези превключватели или Ifs са навсякъде в шоуто.... успех с добавянето на нов enum... така че не е отворен за разширение и затворен за модификация, тъй като трябва да модифицирате съществуващия код, според принципа SOLID.

    Ако вашият избор е 2, предлагам да замените enum със следното, като използвате примера от коментара на @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, а НЕ самия интерфейс. Сега можем да използваме активатора, за да създадем екземпляри на класовете, за да попълним нашите UI контроли:

    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, но аз наистина харесвам enum, но чувствам, че когато се опитвате да разделите логиката с помощта на enum, това е СИЛЕН анти-модел.

коментарите са добре дошли,

person Ernest Gunning    schedule 28.09.2018

Използване на enum в не анти-модел. В някои книги за рефакторинг този код се използва, за да се демонстрира как да се замени с полиморфизъм. Би било добре, когато прекалявате с enums в кода.

person Arseny    schedule 12.10.2010
comment
Не това беше казано. Поисках антимодели, включващи enums. - person Seb Nilsson; 12.10.2010

Виждам наличието на два израза за превключване като симптом на не-OO дизайн както е обяснено по-нататък в този отговор.

person ChrisW    schedule 12.10.2010