#c# #.net #extension-methods #design-patterns #anti-patterns
#c# #.net #методы расширения #шаблоны проектирования #анти-шаблоны
Вопрос:
Я часто видел и использовал перечисления с прикрепленными атрибутами для выполнения некоторых базовых вещей, таких как предоставление отображаемого имени или описания:
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 ООП).
Тем не менее, я признаю, что это можно считать анти-шаблоном. В то же время часть меня считает это достаточно полезным, чтобы анти могло быть слишком строгим.
Комментарии:
1. Вы спрашиваете, не является ли плохой идеей использовать этот шаблон в местах, где вы иначе не использовали бы enum? Это отличный шаблон для связывания метаданных со значениями enum. Это не так здорово, как замена классов.
2. Это можно считать «злоупотреблением» языком; например, почти как использование
dynamic
все время, когда это не оправдано.3. Не совсем в точку (отсюда и комментарий), но у меня есть опасения по поводу кодирования текста, который будет отображаться в атрибуте — или где-нибудь рядом с классом сущностей, если на то пошло. Во-первых, я думаю, что это грубо нарушает «разделение проблем», а во-вторых, нет очевидного способа его локализации. Увы, MS, похоже, поощряет подобные вещи, так что, возможно, я ухожу на обед.
4. @Daniel, методы расширения по определению не имеют ничего общего с классом сущности. Методы расширения, зависящие от пользовательского интерфейса, могут даже полностью находиться на уровне пользовательского интерфейса.
5. @KirkWoll Я имел в виду атрибуты ‘DisplayName’.
Ответ №1:
Я бы счел это плохим шаблоном в C # просто потому, что языковая поддержка объявления атрибутов и доступа к ним настолько ограничена; они не предназначены для хранения большого количества данных. Сложно объявлять атрибуты с нетривиальными значениями, и сложно получить значение атрибута. Как только вам нужно что-то отдаленно интересное, связанное с вашим перечислением (например, метод, который вычисляет что-то в перечислении, или атрибут, содержащий непримитивный тип данных), вам нужно либо преобразовать его в класс, либо поместить другую вещь в какое-то внеполосное место.
На самом деле не сложнее создать неизменяемый класс с некоторыми статическими экземплярами, содержащими ту же информацию, и, на мой взгляд, это более идиоматично.
Комментарии:
1. Это не сложно, нет, с точки зрения объявления перечисления по сравнению с неизменяемым богатым объектом, и для разработчика богатого перечисления есть проблемы, но как насчет использования ? Хотя ваша точка зрения на идиому меня устраивает.
2. Хороший момент при написании идиоматического кода. Вы всегда хотите написать код, который хорошо сочетается с языком / структурой, в которой он живет.
Ответ №2:
Я бы сказал, что это анти-шаблон. Вот почему. Давайте возьмем ваше существующее перечисление (здесь для краткости удаляем атрибуты):
public enum Movement
{
TurnedRight,
TurnedLeft,
Stopped,
Started
}
Теперь, допустим, потребность расширяется, чтобы быть чем-то более точным; скажем, изменение заголовка и / или скорости, превращение одного «поля» в вашем «псевдоклассе» в два:
public sealed class Movement
{
double HeadingDelta { get; private set; }
double VelocityDelta { get; private set; }
// other methods here
}
Итак, у вас есть кодифицированное перечисление, которое теперь должно быть преобразовано в неизменяемый класс, потому что теперь вы отслеживаете два ортогональных (но все еще неизменяемых) свойства, которые действительно принадлежат одному интерфейсу. Любой код, который вы написали против своего «богатого перечисления», теперь должен быть значительно переработан и переработан; в то время как, если бы вы начали с него как класса, у вас, вероятно, было бы меньше работы.
Вы должны спросить, как код будет поддерживаться с течением времени, и будет ли богатое перечисление более удобным в обслуживании, чем класс. Держу пари, что это не было бы более удобным в обслуживании. Кроме того, как указал Маквандер, подход, основанный на классах, в C # более идиоматичен.
Также следует рассмотреть кое-что еще. Если объект неизменяем и является a struct
, а не a class
, вы получаете ту же семантику передачи по значению и незначительные различия в размере сериализации и размере объекта во время выполнения, что и в случае с enum .
Комментарии:
1. Я сталкивался с этим раньше и не могу не согласиться.