【问题标题】:How to resolve class type using enum如何使用枚举解析类类型
【发布时间】:2012-01-22 07:08:07
【问题描述】:

我的项目中有一组类(遵循策略模式)。在主函数中,我从服务器接收一个枚举值,并在此基础上创建一个基类类型的对象。

我正在使用 switch/case 语句来实现这一点。我在某处读到,打开/关闭原则不允许在添加新类时打开函数来添加新的 case 语句。

我正在考虑使用Activator.CreateInstance()。有什么缺点吗。

还有其他方法可以从枚举类型创建对象吗?

在下面添加示例,即使它不是一个完整的策略模式

abstract public class Mammal
{
   public abstract void MakeSound()
}

class Cat:Mammal
{      
    public override  void MakeSound()
    {
        Console.WriteLine("Meow");        
    }    
}

class Dog:Mammal
{

    public override void MakeSound()
    {
         Console.WriteLine("Bow");        
    }    
}

Main()
{

    MammalTypes mammalType = RecieveValueFromServer();
    Mammal mammalBase
    switch(mammalType) // need to make this dynamic depending upon Enum type
    {
        case MammalTypes.Cat:mammalBase = new Cat()
                             break;
        case MammalTypes.Dog:mammalBase = new Dog()
                             break;            
    }

    mammalBase.MakeSound()
}

【问题讨论】:

  • 不是面向对象的。基类不应该知道它的派生类。
  • 很难想象你做了什么。请将您编写的代码添加到您的问题中。

标签: c# oop design-patterns


【解决方案1】:

实现真正 OCP 的一种方法可能如下:

定义一个抽象方法Is来强制每个具体的哺乳动物子类型指定它是否适合给定的枚举值:

abstract public class Mammal
{
    public abstract void MakeSound();

    public abstract bool Is(MammalTypes mammalType);
}

Is 在子类中的实现如下所示:

class Cat : Mammal
{
    // other specific members

    public override bool Is(MammalTypes mammalType)
    {
        return mammalType == MammalTypes.Cat;
    }
}

class Dog : Mammal
{
    // other specific members

    public override bool Is(MammalTypes mammalType)
    {
        return mammalType == MammalTypes.Dog;
    }
}

完成后,我们现在可以创建一个 MammalFactory 类,当给定一个 Mammal 枚举值时,它会扫描可用的类,当它找到匹配项时,它会返回该类的一个实例:

public class MammalFactory
{
    private readonly IEnumerable<Type> _mammalTypes;

    public MammalFactory()
    {
        var currentAssembly = Assembly.GetExecutingAssembly();

        _mammalTypes = currentAssembly.GetTypes()
            .Where(t => typeof(Mammal).IsAssignableFrom(t) && !t.IsAbstract);
    }

    public Mammal Create(MammalTypes mammalType)
    {
        return _mammalTypes
            .Select(type => CreateSpecific(type, mammalType))
            .First(mammal => mammal != null);
    }

    public Mammal CreateSpecific(Type type, MammalTypes mammalEnumType)
    {
        var mammalInstance = (Mammal)Activator.CreateInstance(type);

        return mammalInstance.Is(mammalEnumType) ? mammalInstance : null;
    }
}

最终的用法会是这样的:

var mammalFactory = new MammalFactory();

var guessWhatMammal = mammalFactory.Create(MammalTypes.Cat);

这完全符合 OCP。只需要创建一个新的 Mammal 类,它就可以自动连接并准备好在应用程序中使用。 (无需修改应用程序中的任何其他内容,除了枚举本身)

这种方法存在一些问题:

  • 它只扫描当前正在执行的程序集以查找哺乳动物类型
  • 每次需要测试该类型是否合适时,它都必须创建一个 Mammal 实例

虽然可以解决这些问题,但仍然存在一个问题:复杂性

这很复杂,因为:

  • 我们将所需的代码量增加了一倍
  • 自动接线可能会让刚接触项目的人感到困惑

我认为结论是这样的:设计模式不是严格的规则。仅仅为了符合给定的设计而做某事是不值得的。 相反,我们必须务实,并在模式一致性和有用性/简单性/可读性之间找到完美的平衡。这在很大程度上取决于我们试图解决的问题,并且在许多情况下很可能是您在问题中提出的switch 声明。

【讨论】:

    【解决方案2】:

    您可以使用从枚举类型到函数的字典。这些函数创建您的策略对象:

    public delegate Strategy StrategyFactory();
    var strategyFactories = new Dictionary<MyEnum, StrategyFactory>();
    

    此字典用于根据枚举值创建对象:

    var newStategy = strategyFactories[myEnumValue]();
    

    工厂函数需要以某种方式添加到字典中。为此,您可以公开注册(也可能是取消注册)方法。

    【讨论】:

      【解决方案3】:

      您可以创建一个属性,该属性采用枚举值表示的类型并将其应用于枚举字段,如下所示:

      enum MyEnum {
         [Creates(typeof(FooStrategy))]
         Foo,
         [Creates(typeof(BarStrategy))]
         Bar,
         // etc.
      }
      
      [AttributeUsage(AttributeTargets.Field, Inherited=false, AllowMultiple=false)]
      sealed class CreatesAttribute : Attribute {
          public Type TypeToCreate { get; private set; }
          public CreatesAttribute(Type typeToCreate) {
              TypeToCreate = typeToCreate;
          }
      
          public static IDictionary<T, Func<U>> GenerateLookup<T,U>() {
             var query = from field in typeof(T).GetFields()
                         let creates = field.GetCustomAttriubtes(typeof(CreatesAttribute), false) as CreatesAttribute[]
                         let method = CreationMethod(typeof(U)) // create your type here
                         let key = (T)field.GetValue(null)
                         select new { Key = key, Method = method };
             return q.ToDictionary(item => item.Key, item => item.Method);
          }
      }
      

      留给您的部分是您希望如何创建类的实例。如果它们都具有相同的构造函数,那么此方法将很简单,因为您可以调用 Type.GetConstructor(Type[]) 然后调用 Invoke 您的 ConstructorInfo 实例,或者您可以使用 IoC 容器并从类型中解析实例,不用担心这么多关于不同参数的构造函数。

      然后你可以在你的枚举上为扩展方法创建一个静态类:

      public static class MyEnumExtensions {
          static readonly IDictionary<MyEnumType, MyBaseStrategyType> lookup = 
              CreatesAttribute.GenerateLookup<MyEnumType, MyBaseStrategyType>();
      
          public static MyBaseStrategyType CreateInstance(this MyEnumType key) {
                return lookup[key](/* pass any common constructor values */);
          }
      }
      

      最后你会这样称呼它:

      var myEnum = MyEnumType.Foo;
      var strategy = myEnum.CreateInstance();
      // use your strategy
      

      这应该可以防止您违反开放/封闭原则,并允许您添加任意数量的类,并且只更改可以变得足够通用以直接从枚举值创建策略实例的枚举。

      【讨论】:

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