«Enum как неизменный rich-объект»: это анти-паттерн? - PullRequest
15 голосов
/ 25 октября 2011

Я часто видел и использовал перечисления с прикрепленными атрибутами для выполнения некоторых основных задач, таких как предоставление отображаемого имени или описания:

public enum Movement {
    [DisplayName("Turned Right")]
    TurnedRight,
    [DisplayName("Turned Left")]
    [Description("Execute 90 degree turn to the left")]
    TurnedLeft,
    // ...
}

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

public static string GetDisplayName(this Movement movement) { ... }
public static Movement GetNextTurn(this Movement movement, ...)  { ... }

Следуя этому шаблону, дополнительные существующие или настраиваемые атрибуты могут применяться к полям для выполнения других задач.Это почти так, как если бы перечисление могло работать как простой тип перечисляемого значения и как объект с более богатым неизменяемым значением с несколькими полями:

public class Movement
{
    public int Value { get; set; } // i.e. the type backing the enum
    public string DisplayName { get; set; }
    public string Description { get; set; }
    public Movement GetNextTurn(...) { ... }
    // ...
}

Таким образом, оно может «путешествовать» какпростое поле во время сериализации, быстрое сравнение и т. д., но поведение можно «усвоить» (ала ООП).

Тем не менее, я признаю, что это может рассматриваться как анти-паттерн.В то же время часть меня считает это достаточно полезным, что anti может быть слишком строгим.

Ответы [ 2 ]

7 голосов
/ 25 октября 2011

Я бы посчитал это плохим паттерном в C # просто потому, что языковая поддержка объявления и доступа к атрибутам настолько ограничена; они не предназначены для хранения большого количества данных. Трудно объявлять атрибуты с нетривиальными значениями, и сложно получить значение атрибута. Как только вы захотите связать что-нибудь интересное с вашим перечислением (например, метод, который вычисляет что-то для перечисления, или атрибут, который содержит не примитивный тип данных), вам нужно либо реорганизовать его в класс, либо поместить другое в какое-то внеполосное место.

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

5 голосов
/ 25 октября 2011

Я бы сказал, что это анти-паттерн. Вот почему Давайте возьмем ваш существующий enum (здесь мы рассмотрим атрибуты для краткости):

public enum Movement
{
    TurnedRight,
    TurnedLeft,
    Stopped,
    Started
}

Теперь, скажем, необходимость расширяется, чтобы быть чем-то более точным; скажем, изменение курса и / или скорости, превращение одного «поля» в вашем «псевдоклассе» в два:

public sealed class Movement
{
    double HeadingDelta { get; private set; }
    double VelocityDelta { get; private set; }
    // other methods here
}

Итак, у вас есть кодифицированное перечисление, которое теперь необходимо преобразовать в неизменяемый класс, потому что вы теперь отслеживаете два ортогональных (но все еще неизменных) свойства, которые действительно принадлежат друг другу в одном интерфейсе. Любой код, который вы написали против своего «богатого перечисления», теперь должен быть значительно переработан; тогда как, если бы вы начали с этого как класс, у вас, вероятно, было бы меньше работы.

Вы должны спросить, как код будет поддерживаться с течением времени, и будет ли расширенное перечисление более поддерживаемым, чем класс. Я держу пари, что это не будет более ремонтопригодным. Кроме того, как указал mquander, подход на основе классов более идиоматичен в C #.

Что-то еще, чтобы рассмотреть, также. Если объект является неизменным и является struct, а не class, вы получаете ту же семантику передачи по значению и незначительные различия в размере сериализации и размере времени выполнения объекта, как и при использовании перечисления.

...