如何使用枚举解析类类型
本文关键字:类型 何使用 枚举 | 更新日期: 2023-09-27 18:20:57
我的项目中有一组类(遵循策略模式)。在主函数中,我从服务器接收一个枚举值,并在此基础上创建一个基类类型的对象。
我使用switch/case语句来实现这一点。我在某个地方读到,Open/Closed原则不允许在添加新类时打开函数来添加新的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()
}
实现真正OCP的一种方法可能如下:
定义一个抽象方法Is
,强制Mammal的每个具体子类型指定它是否适合枚举的给定值:
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类型
- 每次需要测试该类型是否合适时,它都必须创建一个Mammal实例
虽然这些问题可以解决,但仍然存在一个问题:复杂性。
这很复杂,因为:
- 我们已经将所需的代码数量增加了一倍
- 对于刚接触该项目的人来说,自动布线可能会让人感到困惑
我认为结论是:设计模式并不是严格的规则。仅仅为了符合给定的设计而做一些事情是不值得的。相反,我们必须务实,在模式一致性和有用性/简单性/可读性之间找到完美的平衡。这在很大程度上取决于我们试图解决的问题,在许多情况下,它很可能是您在问题中提出的switch
语句。
您可以使用从枚举类型到函数的Dictionary。函数创建您的策略对象:
public delegate Strategy StrategyFactory();
var strategyFactories = new Dictionary<MyEnum, StrategyFactory>();
这个字典用于基于枚举值创建对象:
var newStategy = strategyFactories[myEnumValue]();
工厂函数需要以某种方式添加到字典中。为此,您可以公开register(也许还可以注销)方法。
您可以创建一个属性,该属性采用枚举值所表示的类型,并将其应用于枚举字段,如下所示:
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
这应该可以防止您违反打开/关闭原则,并允许您添加任意多的类,并且只更改Enum,该Enum可以变得足够通用,以便直接从枚举值创建策略的实例。