如何避免工厂的开关箱

时间:2013-10-21 15:09:41

标签: c#

我有这个工厂类,它将Foo转换为Bar个对象的列表。 Foo是一个非常复杂的对象,我将其展平为一个简单的Bar对象列表。大约有60种不同的数据位可以从Foo转换为Bar。以下实施有效,但这里有明确的改进空间。

public class FooToBarsConverter
{
    public List<Bar> Convert(Foo foo)
    {
        return Enum.GetValues(typeof(BarTypeEnum))
            .Cast<BarTypeEnum>()
            .Select(barType => CreateBar(foo, barType))
            .Where(newBar => newBar != null)
            .ToList();
    }


    public Bar CreateBar(Foo foo, BarTypeEnum barType)
    {
        switch (barType)
        {
            case BarTypeEnum.TypeA:
                return CreateTypeA(foo);

            case BarTypeEnum.TypeB:
                return CreateTypeB(foo);
        }

        return null;
    }

    private Bar CreateTypeA(Foo foo)
    {
        return new Bar(...);
    }

    private Bar CreateTypeB(Foo foo)
    {
        return new Bar(...);
    }
}

理想情况下,我希望每次添加新的BarType时都不必为交换机编写新案例。也许是类型和委托函数的字典,但仍然需要映射各种类型?是否有任何我可以利用的语言功能来避免这种切换情况使编译器选择创建创建函数?


假设你不介意工厂方法是静态的,这确实可以使它不需要创建大约60个子类来让类型系统为我工作。我认为如果你把它作为工厂的func也不需要静力学,但我还没有那么远。静态不会因为数据转置而特别困扰我

private static readonly IDictionary<BarTypeEnum, Func<Foo, Bar>>
  CreateLookup = new Dictionary<BarTypeEnum, Func<Foo, Bar>>
   {
       { BarTypeEnum.TypeA, CreateTypeA },
       { BarTypeEnum.TypeB, CreateTypeB }
   };

 public Bar Create(Foo foo, BarTypeEnum barType)
 {
     Func<Foo, Bar> createDelegate;
     CreateLookup.TryGetValue(barType, out createDelegate);
     return createDelegate != null ? createDelegate(foo) : null;
 }

 private static Bar CreateTypeA(Foo foo) { ... }
 private static Bar CreateTypeB(Foo foo) { ... }

4 个答案:

答案 0 :(得分:5)

  

我是否可以利用该语言的任何功能来避免这种切换情况使编译器选择create create function?

是。它被称为多态性

观看此视频:Jimmy Bogard - Crafting Wicked Domain Models,了解如何将枚举转换为polimorhic类层次结构。

基本上,您创建一个名为BarTypeEnum的抽象类,它感觉像枚举并创建n个派生类型,每个枚举值一个。然后你可以有这个方法

public abstract Bar CreateBar(Foo foo);

并在每个子类中覆盖它,每个子类返回一个不同的Bar

子类型

e.g。

public override Bar CreateBar(Foo foo)
{
    return CreateTypeA(foo);
}

BTW:他谈到的枚举类是在NuGet上NuGet package Enumeration

修改 我刚检查过,nuget包类和视频不一样。它是通过

实现它的通用,非变形方式

答案 1 :(得分:1)

不是很喜欢它,因为它有点难以阅读,但您可以定义自定义属性,将每个枚举值映射到其方法。您可以使用反射来查找并执行适当的方法。

public class BarChooserAttribute : Attribute
{
    public BarChooserAttribute(BarTypeEnum barType) { BarType = barType; }
    public BarTypeEnum BarType { get; set; }
}

public static class CreateBarMethods
{
    [BarChooser(BarTypeEnum.TypeA)]
    public static Bar CreateTypeA(Foo foo)
    {
        return new Bar { Message = "A" };
    }

    [BarChooser(BarTypeEnum.TypeB)]
    public static Bar CreateTypeB(Foo foo)
    {
        return new Bar { Message = "B" };
    }
}

public static Bar CreateBar(Foo foo, BarTypeEnum barType)
{
    var methodWrapper = typeof(CreateBarMethods).GetMethods(BindingFlags.Public | BindingFlags.Static)
        .Select(m => new { Method = m, Att = (BarChooserAttribute)m.GetCustomAttributes(typeof(BarChooserAttribute), false).Single() })
        .Single(x => x.Att.BarType == barType);
    return (Bar)methodWrapper.Method.Invoke(null, new[] { foo });
}

为了提高性能,您可以将方法一次映射到字典中,并且每次都从字典中检索它们。此外,您可以使用表达式树将方法编译为lambda表达式,因此您只需要进行一次反射,而不是每次进行调用。显着的性能改进,以获得更难以阅读的代码,因此这是一个权衡。

答案 2 :(得分:0)

我讨厌'case',我喜欢Generics因此我稍微改变了FooToBarsConverter的界面:

public interface IFooToBarsConverter
{
    List<Bar> Convert(Foo foo);
    Bar CreateBar<TBarType>(Foo foo) where TBarType : Bar;
}

有一个实现:

public class FooToBarsConverter : IFooToBarsConverter
{
    public List<Bar> Convert(Foo foo)
    {
        return new List<Type>
        {
            typeof(Bar.BarA),
            typeof(Bar.BarB)
        }.Select(it => CreateBar(foo, it))
        .ToList();
    }

    public Bar CreateBar<T>(Foo foo)
        where T : Bar
    {
        return CreateBar(foo, typeof(T));
    }

    private Bar CreateBar(Foo foo, Type barType)
    {
        return typeof(Bar).IsAssignableFrom(barType)
            ? (Bar)Activator.CreateInstance(barType, foo)
            : null;
    }
}

public class Foo
{
}

public abstract class Bar
{
    private Bar(Foo foo)
    {

    }

    public class BarA : Bar
    {
        public BarA(Foo foo)
            : base(foo)
        {
        }
    }

    public class BarB : Bar
    {
        public BarB(Foo foo)
            : base(foo)
        {
        }
    }
}

......以及测试它的测试:

    [TestMethod]
    public void TestMethod()
    {
        // arrange
        var foo = new Foo();
        var target = new FooToBarsConverter();

        // act + assert
        var list = target.Convert(foo);
        list.Should().HaveCount(2);
        list.Should().NotContainNulls();

        var typeA = target.CreateBar<Bar.BarA>(foo);
        typeA.Should().BeAssignableTo<Bar.BarA>();

        var typeB = target.CreateBar<Bar.BarB>(foo);
        typeB.Should().BeAssignableTo<Bar.BarB>();
    }

答案 3 :(得分:0)

就我个人而言,我不介意工厂方法中的switch,它是可读的,它很整洁并且不会影响工厂方法的最终目标 - 保持初始化代码在一起。

然而,话虽如此,我想知道一个自定义属性是否可以为你整理一下。假设所有CreateBarX方法都创建Bar的实例,从Foo初始化特定属性。

[System.AttributeUsage(System.AttributeTargets.Field)]
public class FooConverter : System.Attribute
{
    public string Parameters;

    public Bar GetInstance(Foo foo)
    {
        var propNames = String.IsNullOrEmpty(Parameters) ? new string[] { } : Parameters.Split(',').Select(x => x.Trim());
        var parameters = foo.GetType().GetProperties().Where(x => propNames.Contains(x.Name)).Select(x => x.GetValue(foo));
        return (Bar)Activator.CreateInstance(typeof(Bar), parameters.ToArray());
    }
}

// extension helpers
public static class EnumExt
{
    public static Bar GetInstance(this BarTypeEnum value, Foo foo)
    {
        var converterAttr = value.GetAttribute<FooConverter>();
        return converterAttr != null ? converterAttr.GetInstance(foo) : null;
    }

    public static T GetAttribute<T>(this System.Enum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());
        var attributes = fi.GetCustomAttributes(typeof(T), false);
        return attributes.Length > 0 ? (T)attributes[0] : default(T);
    }   
}

这将允许你做

public enum BarTypeEnum
{
    [FooConverter] // no properties mapped
    TypeA,
    [FooConverter(Parameters="Prop1")] // map Prop1 from Foo to Bar
    TypeB,
    TypeC, // no instance
    [FooConverter(Parameters="Prop1, Prop2")] // map Prop1/2 from Foo to Bar
    TypeD, 
    TypeE // no instance
}

public List<Bar> Convert(Foo foo)
{
    return Enum.GetValues(typeof(BarTypeEnum))
        .Cast<BarTypeEnum>()
        .Select(barType => barType.GetInstance(foo))
        .Where(newBar => newBar != null)
        .ToList();
}

这就是你所需要的!

但是,对于参数注入这种方法存在一些限制,CreateInstance只会根据与数据类型匹配的签名匹配构造函数,即

 // this will call Bar(string prop1, string prop2)
 Activator.CreateInstance(typeof(Bar), new object[] { "Property1", "Property2" });

 // where as this will car Bar(string prop1)
 Activator.CreateInstance(typeof(Bar), new object[] { "Property2" });

顺序也很重要

 // this will call Bar(string prop1, string prop2) so Prop1 = "Property2"
 Activator.CreateInstance(typeof(Bar), new object[] { "Property2", "Property1" });

然而,有很多方法可以解决这个问题 - 在大多数情况下,这可能会很好。