我的工厂看起来像下面的代码片段。 Foo是Bar的包装类,在大多数情况下(但不是全部),有一个1:1的映射。作为一项规则,Bar对Foo一无所知,但Foo采用了Bar的实例。这样做有更好/更清洁的方法吗?
public Foo Make( Bar obj )
{
if( obj is Bar1 )
return new Foo1( obj as Bar1 );
if( obj is Bar2 )
return new Foo2( obj as Bar2 );
if( obj is Bar3 )
return new Foo3( obj as Bar3 );
if( obj is Bar4 )
return new Foo3( obj as Bar4 ); // same wrapper as Bar3
throw new ArgumentException();
}
乍一看,这个问题可能看起来像是重复的(可能是这样),但我还没有看到一个完全相同的问题。这是一个很接近但不完全的:
答案 0 :(得分:4)
如果这些是引用类型,则在as
之后调用is
是不必要的费用。通常的习惯用是使用as
进行投射并检查是否为空。
从微优化中退一步,看起来您可以使用链接到的文章中的一些技巧。具体而言,您可以创建一个键入该类型的字典,其值为构造实例的委托。代表将带一个(孩子的)Bar并返回一个(孩子的)Foo。理想情况下,Foo的每个孩子都会将自己注册到字典中,这可能在Foo本身内是静态的。
以下是一些示例代码:
// Foo creator delegate.
public delegate Foo CreateFoo(Bar bar);
// Lookup of creators, for each type of Bar.
public static Dictionary<Type, CreateFoo> Factory = new Dictionary<Type, CreateFoo>();
// Registration.
Factory.Add(typeof(Bar1), (b => new Foo1(b)));
// Factory method.
static Foo Create(Bar bar)
{
CreateFoo cf;
if (!Factory.TryGetValue(bar.GetType(), out cf))
return null;
return cf(bar);
}
答案 1 :(得分:1)
我不确定你真正希望实现的目标。我可能会试着让它更通用。
您可以在Foo上使用它支持的Bar的属性,然后在初始化阶段创建一个列表。我们正在做很多这样的事情,它使得添加和连接新类非常容易。
private Dictionary<Type, Type> fooOfBar = new Dictionary<Type, Type>();
public initialize()
{
// you could scan all types in the assembly of a certain base class
// (fooType) and read the attribute
fooOfBar.Add(attribute.BarType, fooType);
}
public Foo Make( Bar obj )
{
return (Foo)Activator.CreateInstance(fooOfBar(obj.GetType()), obj);
}
答案 2 :(得分:1)
在您的问题中,从一组类到另一组类的映射看起来非常简单。但是,通常需要根据输入类调用特定的构造函数和/或设置输出类的属性。有时您可以使用AutoMapper等库。
但是,在其他情况下,您需要为每次转换创建特定的工厂方法。在您的情况下,从Foo1
创建Bar1
,从Foo2
等创建Bar2
是工厂方法:
Foo1 CreateFoo1(Bar1 bar1) { ... }
Foo2 CreateFoo2(Bar2 bar2) { ... }
您可以将所有这些工厂方法作为委托存储在字典中,然后使用输入类型选择工厂来创建输出类型。
var inputType = input.GetType();
var factory = factories[inputType];
var output = factory(input);
通过使用反射,您可以构建此字典并避免在调用工厂时使用反射的额外成本,您可以使用表达式来编译小型lambda,它将执行所需的转换和调用。
此功能可以通过基类公开,该基类假定输入类型和输出类型是并行类层次结构。例如,在您的情况下,所有Foo#
类可能具有Foo
作为基类,并且所有Bar#
类可能具有Bar
作为基础关闭。但是,如果不是这样,则所有类都将object
作为基类,因此这种方法仍然有效。
您的派生工厂类看起来像这样:
public class FooFactory : TypeBasedFactory<Bar, Foo>
{
private Foo1 CreateFoo1(Bar1 bar1)
{
return new Foo1(bar1.Id, bar1.Name, ...);
}
private Foo2 CreateFoo2(Bar2 bar2)
{
return new Foo2(bar2.Description, ...);
}
}
注意工厂方法是如何私有的。它们不打算直接调用。相反,TypeBasedFactory
声明了一个CreateFrom
方法,它将调用正确的工厂:
var fooFactory = new TypeBasedFactory<Bar, Foo>();
var foo = fooFactory.CreateFrom(bar);
以下是TypeBasedFactory
的代码:
public abstract class TypeBasedFactory<TInput, TOutput>
where TInput : class where TOutput : class
{
private readonly Dictionary<Type, Func<TInput, TOutput>> factories;
protected TypeBasedFactory()
{
factories = CreateFactories();
}
private Dictionary<Type, Func<TInput, TOutput>> CreateFactories()
{
return GetType()
.GetMethods(
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance)
.Where(methodInfo =>
!methodInfo.IsAbstract
&& methodInfo.GetParameters().Length == 1
&& typeof(TOutput).IsAssignableFrom(methodInfo.ReturnType))
.Select(methodInfo => new
{
MethodInfo = methodInfo,
methodInfo.GetParameters().First().ParameterType
})
.Where(factory =>
typeof(TInput).IsAssignableFrom(factory.ParameterType)
&& !factory.ParameterType.IsAbstract)
.ToDictionary(
factory => factory.ParameterType,
factory => CreateFactory(factory.MethodInfo, factory.ParameterType));
}
private Func<TInput, TOutput> CreateFactory(MethodInfo methodInfo, Type parameterType)
{
// Create this Func<TInput, TOutput>: (TInput input) => Method((Parameter) input)
var inputExpression = Expression.Parameter(typeof(TInput), "input");
var castExpression = Expression.Convert(inputExpression, parameterType);
var callExpression = Expression.Call(Expression.Constant(this), methodInfo, castExpression);
var lambdaExpression = Expression.Lambda<Func<TInput, TOutput>>(callExpression, inputExpression);
return lambdaExpression.Compile();
}
public TOutput CreateFrom(TInput input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
var inputType = input.GetType();
Func<TInput, TOutput> factory;
if (!factories.TryGetValue(inputType, out factory))
throw new InvalidOperationException($"No factory method defined for {inputType.FullName}.");
return factory(input);
}
}
CreateFactories
方法使用反射来查找能够从TOuput
创建TInput
(可能是派生类)的公共方法和私有方法(非抽象派生)类)。
CreateFactory
方法创建一个Func<TInput, TOutput>
,在调用工厂方法之前执行所需的向下转换。一旦编译了lambda,调用它就没有反射开销。
构造一个派生自TypeBasedFactory
的类将使用反射来构建工厂字典,因此您应该避免创建多个实例(即工厂应该是单例)。