【问题标题】:Factory Method Using Is/As Operator使用 Is/As 运算符的工厂方法
【发布时间】:2010-06-02 15:48:32
【问题描述】:

我有一个类似于以下 sn-p 的工厂。 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();
}

乍一看,这个问题可能看起来像一个重复的问题(也许是),但我还没有看到一个完全一样的问题。这是一个接近但不完全的一个:

Factory based on Typeof or is a

【问题讨论】:

    标签: c# factory


    【解决方案1】:

    如果这些是引用类型,那么在is 之后调用as 是不必要的开销。通常的习惯用法是使用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);
    }
    

    【讨论】:

    • 对。我知道“is”只是一个包装好的“as”,但有很长的列表: Bar1 bar1 = obj as Bar1; if (bar1 != null ) return new Foo1( bar1 );对我来说似乎非常丑陋。 ;-)
    • 我会继续看这个,因为我不知道比你在做什么更好的方法,这就是我解决这个问题的方法......
    • 游泳,如果你不需要灵活性,那么有一个简单,快速但丑陋的方法并不是那么糟糕。为了灵活性,请考虑字典技术。
    • 亲爱的投票者(大概是 Stefan):你可以投票,但如果你能解释原因,我会很高兴的。
    • 嗯,我没有在这里投票,而且我从来没有在没有评论的情况下投反对票。投票给我的可能是同一个人?
    【解决方案2】:

    我不确定您真正想要实现什么。我可能会尝试使其更通用。

    您可以在 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);
    }
    

    【讨论】:

    • 我提出了类似的建议,但没有反思。字典的值可以是委托而不是原始类型。
    • 我添加了一个代码示例以使其更加清晰。虽然我没有对其进行基准测试,但我强烈怀疑委托方法会比Activator.CreateInstance 更快。
    • 我刚刚尝试过这种方法,我非常喜欢它。它非常干净且易于(微不足道)维护。这个特定工厂的最佳性能不是必需品也不是必需品,否则,我可能会接受@Steven 的回复,我最初是这样做的。
    • @Swim:你不必决定。如果使用委托,则可以显式连接构造函数或通过 Activator 创建它。
    【解决方案3】:

    在您的问题中,从一组类到另一组类的映射看起来很简单。但是,您通常希望根据输入类调用特定的构造函数和/或设置输出类的属性。有时你可以使用像AutoMapper 这样的库。

    但是,在其他情况下,您需要为每次转换创建特定的工厂方法。在您的情况下,从Bar1Foo2Bar2 等创建Foo1 将是工厂方法:

    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 方法使用反射来查找能够从 TInput(非抽象派生类)创建 TOuput(可能是派生类)的公共和私有方法。

    CreateFactory 方法创建一个Func&lt;TInput, TOutput&gt;,在调用工厂方法之前执行所需的向下转换。一旦 lambda 被编译,调用它就没有反射开销。

    构造一个派生自TypeBasedFactory 的类将使用反射来构建工厂字典,因此您应该避免创建多个实例(即,工厂应该是单例)。

    【讨论】:

      猜你喜欢
      • 2011-11-03
      • 1970-01-01
      • 2013-08-25
      • 1970-01-01
      • 2023-04-05
      • 2016-03-13
      • 2010-12-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多