Saturday, November 20, 2010

Не распыляйте логику или информация в Enum

Для перечисления типов отчетов нам нужно было где-то сохранить название отчета и имя файла, хранящего его разметку.
public enum ReportTypes
{
    ComponentList = 1,
    ComponentAudit = 2,
}

Самый простой вариант – добавить специальные методы, возвращающие по типу соответствующую информацию. Внутри методов, разумеется, торчит switch:
public static string GetReportTitle(ReportTypes type)
{
    switch (type)
    {
        case ReportTypes.ComponentList:
            return "Список компонентов";
        case ReportTypes.ComponentAudit:
            return "Аудит компонентов";
    }

    return string.Empty;
}

public static string GetReportFileName(ReportTypes type)
{
    switch (type)
    {
        case ReportTypes.ComponentList:
            return "ComponentList.frx";
        case ReportTypes.ComponentAudit:
            return "ComponentAudit.frx";
    }

    return string.Empty;
}

Первая очевидная проблема в этом коде – если вдруг мы передадим неверный тип отчета, то код об этом не скажет ничего. Исправить это не сложно:
public static string GetReportTitle(ReportTypes type)
{
    switch (type)
    {
        case ReportTypes.ComponentList:
            return "Список компонентов";
        case ReportTypes.ComponentAudit:
            return "Аудит компонентов";
        default:
            throw new ArgumentException("ReportTypes");
    }
}

public static string GetReportFileName(ReportTypes type)
{
    switch (type)
    {
        case ReportTypes.ComponentList:
            return "ComponentList.frx";
        case ReportTypes.ComponentAudit:
            return "ComponentAudit.frx";
        default:
            throw new ArgumentException("ReportTypes");
    }
}

Стало получше, но логика раскидана в трех местах – собственно в перечислении и в двух методах.
Можно попробовать собрать информацию с помощью класса, возвращающего все поля сразу:
public class ReportInfo
{
    public string Title { get; set; }
    public string FileName { get; set; }
}

public static ReportInfo GetReportInfo(ReportTypes type)
{
...
}

Теперь логика раскидана всего в двух местах, но мне не нравится и это. При добавлении нового типа отчета в перечисление нужно вспоминать, где же лежит метод, возвращающий информацию.
В .NET если очень мощный механизм, позволяющий решить этот вопрос – атрибуты. Создаем атрибут, хранящий информацию об отчете:
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
    public class ReportAttribute : Attribute
    {
        public ReportAttribute(string title, string fileName)
        {
            this.ReportTitle = title;
            this.ReportFile = fileName;
        }

        public string ReportTitle
        {
            get; protected set;
        }

        public string ReportFile
        {
            get; protected set;
        }
    }

Добавляем информацию прямо в перечисление:
public enum ReportTypes
{
    [ReportAttribute("Список компонентов", "ComponentList.frx")]
    ComponentList = 1,
    [ReportAttribute("Аудит компонентов", "ComponentAudit.frx")]
    ComponentAudit = 2,
}

Делаем класс, работающий с этими атрибутами:
public static class ReportTypesHelper
{
    private static Type type = typeof(ReportTypes);

    /// <summary>
    /// Атрибут отчета для конкретного типа отчета
    /// </summary>
    public static ReportAttribute GetReportAttribute(ReportTypes reportType)
    {
        FieldInfo propInfo = type.GetField(reportType.ToString());
        if (propInfo != null)
        {
            var attribs = propInfo.GetCustomAttributes(typeof(ReportAttribute), false) as ReportAttribute[];

            if (attribs != null && attribs.Length > 0)
            {
                return attribs[0];
            }
        }

        return null;
    }

    /// <summary>
    /// Список отчетов
    /// </summary>
    /// <returns></returns>
    public static IEnumerable<ReportAttribute> GetReportList()
    {
        var obj = Enum.GetValues(type);

        foreach (ReportTypes item in obj)
        {
            yield return GetReportAttribute(item);
        }
    }
}

Все. Теперь можно легко получить и список всех отчетов и информацию о конкретном отчете. А все нужная информация хранится в одном месте. Если скорость получения информации критична (в моем случае это было совершенно не принципиально), можно сделать кэш и хранить один раз полученную информацию в нем.

No comments: