【问题标题】:Factory driven by enum attributes由枚举属性驱动的工厂
【发布时间】:2014-08-23 02:42:47
【问题描述】:

我有一种工厂模式看起来像这样的情况:

 enum Food {      
  Cake,      
  Cookies,      
  Milk,
  CannedBeans
}

public static class FoodMetaDataFactory{    
    public static IFood GetMetaData(Food foodType){  //takes a food enum value as a parameter
       switch(foodType){
          case Food.Milk:
             return new MilkMetaData();
          case Food.CannedBeans:
             return new CannedBeansMetaData();
          case Food.Cookies:
             return new CookiesMetaData();
          case Food.Cake:
             return new CakeMetaData();
       }
    }    
}

但是,我宁愿有像这样的声明性模式

enum Food {
  [FoodMetaDataAttribute(typeof(CakeMetaData))]    
  Cake,
  [FoodMetaDataAttribute(typeof(CookiesMetaData))]   
  Cookies,
  [FoodMetaDataAttribute(typeof(MilkMetaData))]   
  Milk,
  [FoodMetaDataAttribute(typeof(CannedBeansMetaData))]   
  CannedBeans
}

public static class FoodMetaDataFactory{    
    public static IFood GetMetaData(Food foodType){  //takes a food enum value as a parameter
        //reflectively retrieve FoodMetaDataAttribute
        Type enumType = typeof(Food);
        var memInfo = enumType.GetMember(foodType.ToString());
        //assume one item returned
        var foodMetaDataAttributes = memInfo[0].GetCustomAttributes(typeof(FoodMetaDataAttribute),
false);
        // now access the property telling us the concrete type of the metadata class(this is the type passed in the attribute's declaration
        Type targetType = ((FoodMetaDataAttribute)foodMetaDataAttributes[0]).MetaDataProviderType;     

        //not done yet, now we need to reflectively instantiate targetType, something like this            
        ConstructorInfo ctor = targetType.GetConstructor(new[] { });
        //invoke the constructor, returning concrete instance such as CakeMetaData
        return ctor.Invoke(new object[] {}) as IFood;     
    }    
}

[AttributeUsage(AttributeTargets.Field)]
public class FoodMetaDataAttribute : Attribute
{      
   public FoodMetaDataAttribute(Type metaDataProviderType){
      MetaDataProviderType = metaDataProviderType;
   }
   public Type MetaDataProviderType { get; set; }
}

我喜欢这个,因为任何向枚举添加新值的人都清楚他们需要一个元数据类并在属性中声明它。这个 IMO 比必须记住在工厂修改开关盒要好。

起初看起来很简单,直到我开始考虑 GetMetaData 的实现,它必须反射地检索属性、typeof 参数,然后反射地实例化 MetaData 类。 我没有创建属性类的经验,所以这个问题的主要驱动力是希望也许有一种更简单的方法可以用属性来完成。如果属性类没有那么多限制,例如在使用泛型类型时,我会以我喜欢的方式完成此操作,并具有一定的编译时安全性。

这个提议的解决方案没有编译时安全性。您可以将类型传递给不实现 IFood 的属性,这是 MetaData 类(如 MilkMetaData)的最低要求。如果属性允许泛型类型参数,我会使用它而不是 typeof 并且可以应用 where T:IFood

有没有更好的方法来利用属性来完成从枚举值到具体类的映射?

【问题讨论】:

  • 这道题需要类图
  • 经典类图描述了继承关系。他们甚至没有描述组合,也没有描述“这段代码调用这个类”(用 UML 术语来说,这是序列图的工作)。我不确定类图中会显示什么,因为这里没有继承。 你能指出你在哪里感到困惑,我会尝试改进这个问题吗?
  • 我不明白其中涉及哪些具体类或实例化什么。你说这里没有继承,但多态性可能是解决问题的一个非常简单的方法。
  • 您显然有大量的数据和行为附加到每种食物种类。为什么不将Food 设为具有私有构造函数的类并为每种类型(例如牛奶、蛋糕等)创建一个公共静态属性。然后它们将具有枚举的“感觉”,但可以具有其他属性/方法。
  • 这给我一种代码味道,如果特定的枚举值和元数据紧密耦合,只需重构为类。

标签: c#


【解决方案1】:

在这些情况下,我通常会创建一个工厂字典,例如:

private IDictionary<MyEnum, Func<IMyInterface>> Factories = new Dictionary<MyEnum, Func<IMyInterface>> {
    { MyEnum.MyValue, () => new MyType() },
    // etc.
}

简单且易于维护、扩展或验证。您可以通过以下方式创建实例:

IMyInterface instance;
if(!Factories.TryGetValue(enumValue, out instance))
    throw new Exception(string.Format("No factory for enum value {0}", enumValue));
return instance;

请注意,将枚举与实际实例分开应该是的事情(从数据中拆分实现)。否则,我建议您只需将实际类型传递给泛型方法。

【讨论】:

  • 这是一个很好的方法,也是我去年一直在使用的方法,而不是 switch 语句。很干净。
【解决方案2】:

我不能 100% 确定没有更好的完全不同的方法,但是您可以在当前代码中改进几件事:

你可以使用Activator.CreateInstance(type)而不是获取构造函数:

return Activator.CreateInstance(targetType) as IFood;

您还可以考虑使用某种缓存,以避免在每次调用时都执行所有与反射相关的工作。您可以使用简单的字典为每个枚举值存储一个具体的 IFood 实现实例:

public static class FoodMetaDataFactory
{
    private static Dictionary<Food, IFood> _cache = new Dictionary<Food, IFood>();

    public static IFood GetMetaData(Food foodType)
    {  //takes a food enum value as a parameter
        IFood value;
        if (!_cache.TryGetValue(foodType, out value))
        {
            lock (_cache)
            {
                if (!_cache.TryGetValue(foodType, out value))
                {
                    var enumType = typeof(Food);
                    var memberInfo = enumType.GetMember(foodType.ToString());
                    var foodMetaDataAttributes = memberInfo[0].GetCustomAttributes(typeof(FoodMetaDataAttribute), false);

                    var targetType = ((FoodMetaDataAttribute)foodMetaDataAttributes[0]).MetaDataProviderType;

                    value = Activator.CreateInstance(targetType) as IFood;
                    _cache.Add(foodType, value);
                }
            }
        }

        return value;
    }
}

或者,如果您需要每次调用都返回新实例而不是共享实例,您可以使用表达式树,在针对给定的 enum 值第一次调用 GetMetaData 时生成 Func&lt;IFood&gt; lambda 表达式,并且稍后调用它而不是反射处理。

关于编译时安全性:恐怕您必须为此编写自己的自定义检查,例如作为 FxCop 自定义规则,或者使用 Roslyn,如果您使用的是最新(测试版)的 Visual Studio。

【讨论】:

    【解决方案3】:

    这里是新的。从最后开始,如果您想使用这些 Enum 值进行显示,最好的方法是使用显示字符串(或 CSV)的属性来装饰它们,但如果需要复杂的值,您应该使用创建新食品类型的工厂方法。基类可以包含公共值,而每个连续的子类都有可以始终提供给 UI 机制的细节。

    这仅适用于每种类型都创建自己的视图或视图在类型之间是通用的。这类似于第二种思路中的依赖注入。

    但是,如果您想添加表示代码路径的其他枚举,则必须始终更新您的 Controller\ViewModel,除非有一些通用模型始终用于显示。

    我不知道你的代码库,所以我不知道工厂模式或适配器模式需要哪种类型的重构。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-16
      • 2011-02-16
      • 1970-01-01
      相关资源
      最近更新 更多