如何避免工厂中的开关情况
本文关键字:开关 情况 何避免 工厂 | 更新日期: 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
方法都创建一个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" });
但是,有一些方法可以解决此问题 - 在大多数情况下,这可能会很好地工作。
我讨厌'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>();
}