工厂由枚举属性驱动

时间:2014-08-23 02:42:47

标签: c#

我有一种工厂模式似乎合适的情况:

 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

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

3 个答案:

答案 0 :(得分:3)

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

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;

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

答案 1 :(得分:1)

我并不是百分之百确定没有一种完全不同的方法可以做得更好,但在目前的代码中只有几点可以改进:

您可以使用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;
    }
}

或者如果您需要每次调用以返回新实例而不是共享实例,则可以使用表达式树,在第一次调用Func<IFood>时生成GetMetaData lambda表达式enum 1}}值,稍后调用它而不是反射处理。

关于编译时的安全性:我担心您必须自己编写自定义检查,例如:作为FxCop自定义规则,或者使用Roslyn的东西,如果你使用的是最新的(beta)版本的Visual Studio。

答案 2 :(得分:0)

新来的。要从最后开始,如果要使用这些枚举值进行显示,最好的方法是使用显示字符串(或CSV)属性来装饰它们,但如果值需要很复杂,那么应该使用创建新Food类型的Factory Method。基类可以包含公共值,而每个连续的Child类都具有可以随时提供给UI机制的细节。

这仅适用于每种类型创建自己的视图或视图在类型之间通用的情况。这与第二种想法中的依赖注入相似。

但是如果你想添加代表代码路径的额外枚举,你必须总是更新你的Controller \ ViewModel,除非有一些总是用于显示的通用模型。

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