3

我有这个工厂类,它将 aFoo转换为Bar对象列表。Foo 是一个非常复杂的对象,我将其展平为一个简单Bar对象的列表。大约有 60 种不同的数据位可以从 aFoo转换为 a 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

4 回答 4

5

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

是的。这叫多态

查看此视频:Jimmy Bogard - Crafting Wicked Domain Models关于如何将枚举转换为 polimorhic 类层次结构。

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

public abstract Bar CreateBar(Foo foo);

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

例如

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

顺便说一句:他谈到的枚举类在 NuGet 上作为NuGet 包 Enumeration

编辑 我刚刚检查过,nuget 包类与视频不同。这是一种通用的、非多态的实现方式

于 2013-10-21T16:05:53.537 回答
1

就我个人而言,我不介意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" });

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

于 2013-10-22T09:15:46.827 回答
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 表达式,因此您只需进行一次反射,而不是每次调用。显着的性能改进,获得显着难以阅读的代码,所以这是一个权衡。

于 2013-10-21T15:56:33.747 回答
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>();
    }
于 2013-10-21T16:37:13.967 回答