C# 工厂如何避免开关箱

C# 工厂如何避免开关箱,c#,C#,我有一个工厂类,它将Foo转换为Bar对象列表。Foo是一个非常复杂的对象,我将它展平为一个简单的Bar对象列表。大约有60个不同的数据位可以从Foo转换为Bar。下面的实现是可行的,但这里有一定的改进空间 public class FooToBarsConverter { public List<Bar> Convert(Foo foo) { return Enum.GetValues(typeof(BarTypeEnum))

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

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

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表达式,因此只需进行一次反射,而不是每次调用。显著的性能改进,使代码更难阅读,因此这是一种折衷

我可以利用语言的任何特性来避免这种切换情况,使编译器选择create函数吗

对。这叫做多态性

查看此视频:关于如何将枚举转换为polimorhic类层次结构

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

public abstract Bar CreateBar(Foo foo);
并在每个子类中重写它,每个子类返回不同的Bar子类型

e、 g

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

编辑
我刚检查过,nuget package类与视频不一样。这是一种通用的、非同构的实现方法,尽管我讨厌“case”,我喜欢泛型,因此我稍微更改了FooToBarsConverter的接口:

public interface IFooToBarsConverter
{
    List<Bar> Convert(Foo foo);
    Bar CreateBar<TBarType>(Foo foo) where TBarType : Bar;
}
公共接口IFooToBarsConverter
{
列表转换(Foo-Foo);
Bar CreateBar(Foo-Foo),其中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)
        {
        }
    }
}
公共类footobarscoverter:ifootobarscoverter
{
公共列表转换(Foo-Foo)
{
返回新列表
{
类型(Bar.BarA),
类型(条形倒钩)
}.Select(it=>CreateBar(foo,it))
.ToList();
}
公共酒吧CreateBar(Foo-Foo)
T:酒吧在哪里
{
返回CreateBar(foo,typeof(T));
}
专用条CreateBar(Foo-Foo,类型barType)
{
返回类型(Bar).IsAssignableFrom(barType)
?(Bar)Activator.CreateInstance(barType,foo)
:null;
}
}
公开课Foo
{
}
公共抽象类栏
{
私人酒吧(富富)
{
}
公共类巴拉:酒吧
{
公共巴拉(福福)
:base(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>();
    }
[TestMethod]
公共void TestMethod()
{
//安排
var foo=new foo();
var target=新的FooToBarsConverter();
//行动+断言
var list=target.Convert(foo);
list.Should().HaveCount(2);
list.Should().NotContainNulls();
var typeA=target.CreateBar(foo);
typeA.Should().BeAssignableTo();
var typeB=target.CreateBar(foo);
typeB.Should().BeAssignableTo();
}

就我个人而言,我不介意在工厂方法中使用
开关,它可读、整洁,并且不会影响工厂方法的最终目标—将初始化代码保持在一起

不过,尽管如此,我想知道自定义属性是否可以为您整理一下。假设所有
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);
    }   
}
订购也很重要

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

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

您必须更新以添加新的
CreateBarType
方法无论如何,额外的case语句会有什么危害?对我来说,它的可读性非常好,任何其他东西都可能会牺牲这一点。您能传入实际的类型而不是表示该类型的枚举吗?这样,创建条就可以是
return Activator.CreateInstance(barType,new object[]{foo})
@axblount问题是我只有一个
bar
对象,类似于
new bar{Id=123,Type=barType,Data1=“xyz”,Data2=“ghj”}
。我想我可以为每个
BarType
创建一个强类型版本,它继承自Bar,然后在
create
中这样使用它。我希望有人能想出更好的方法,但我认为它不存在。Bob叔叔甚至屈服于工厂中使用的switch语句,如果你读过他的书,你可能会注意到他讨厌switch语句。我很想找个天才来证明我错了。你可以用
字典
。这样,所有的信息都被打包在这个字典的初始值设定项中。很好的解决方案,我想这样它真的加强了SRP。当enum已经存在并且是数据库的类型标识符时,创建所有这些子类似乎有些过分。如果我打算在数据库和聚合视图中包含所有60个表,我想这对我来说可能更有意义,但我不想特别这么做。我喜欢这个解决方案,很高兴知道如果需要,可以提供性能增强。表情树是我认为我真的应该了解更多的东西之一。如果你真的开始观察表情树,准备好用你的头在墙上敲一会儿。即使有教程,语法也不是直观的,而且很容易犯编译器或运行时无法解决的错误。我可能
[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();
}
 // 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" });