如何避免工厂中的开关情况

本文关键字:开关 情况 何避免 工厂 | 更新日期: 2023-09-27 18:36:20

我有一个工厂类,它将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) { ... }

如何避免工厂中的开关情况

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

是的。这就是所谓的多态性

观看此视频:Jimmy Bogard - 制作邪恶领域模型,了解如何将枚举转换为 polimorhic 类层次结构。

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

public abstract Bar CreateBar(Foo foo);
并在每个子类

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

例如

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

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

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

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

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 表达式,因此您只需执行一次反射,而不是每次进行调用。显著的性能改进,以获得更难阅读的代码,因此这是一个权衡。

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

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

[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" });

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

我讨厌'case',我喜欢泛型,因此我稍微改变了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>();
    }