"Enum as immutable rich-object": это анти-шаблон?
Я часто видел и использовал перечисления с прикрепленными атрибутами, чтобы сделать некоторые основные вещи, такие как предоставление отображаемого имени или описания:
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(...) { ... }
// ...
}
Таким образом, он может "путешествовать" как простое поле во время сериализации, быстро сравниваться и т.д., но поведение может быть "интернализировано" (ala OOP).
Тем не менее, я признаю, что это можно считать анти-шаблоном. В то же время часть меня считает это достаточно полезным, что анти может быть слишком строгим.
Ответы
Ответ 1
Я считаю, что это плохой шаблон на С# просто потому, что поддержка языка для объявления и доступа к атрибутам настолько искалечена; они не предназначены для хранения очень больших данных. Это боль, чтобы объявить атрибуты с нетривиальными значениями, и это боль, чтобы получить значение атрибута. Как только вы захотите что-то отдаленно интересное, связанное с вашим перечислением (например, метод, который вычисляет что-то в перечислении или атрибут, который содержит не примитивный тип данных), вам нужно либо реорганизовать его в класс, либо поместить другое в какое-то внеполосное место.
Не сложнее сделать неизменяемый класс с некоторыми статическими экземплярами, содержащими ту же информацию, и, на мой взгляд, это более идиоматично.
Ответ 2
Я бы сказал, что это анти-шаблон. Вот почему. Возьмем ваш существующий 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
}
Итак, у вас есть кодифицированное перечисление, которое теперь должно быть преобразовано в неизменяемый класс, потому что теперь вы отслеживаете два ортогональных (но все еще неизменяемых) свойства, которые действительно принадлежат друг другу в одном интерфейсе. Любой код, который вы написали против вашего "богатого enum", теперь должен быть потрошен и переработан значительно; тогда как если бы вы начали с него как с классом, у вас, вероятно, было бы меньше работы.
Вы должны спросить, как код будет поддерживаться с течением времени, и если богатое перечисление будет более удобным для обслуживания, чем класс. Моя ставка заключается в том, что он не будет более ремонтопригодным. Кроме того, как указывал mquander, подход на основе классов более идиоматичен в С#.
Что-то еще нужно учитывать. Если объект является неизменным и является struct
, а не class
, вы получаете такую же семантику pass-by-value и незначительные различия в размере сериализации и размере времени выполнения объекта, как и с перечислением.