8

根据传递给工厂类的泛型类型实例化对象的最有效方法是什么,例如:

public class LoggerFactory
{
    public static ILogger<T> Create<T>()
    {
        // Switch Statement?
        // Generic Dictionary?
        // EX.: if "T" is of type "string": return (ILogger<T>)new StringLogger();
    }
}

你会怎么做?哪个分支语句?ETC...

4

8 回答 8

18

我认为最好保持简单,也许是这样的:

public static class LoggerFactory
{
    static readonly Dictionary<Type, Type> loggers = new Dictionary<Type, Type>();

    public static void AddLoggerProvider<T, TLogger>() where TLogger : ILogger<T>, new()
    {
        loggers.Add(typeof(T), typeof(TLogger));
    }

    public static ILogger<T> CreateLogger<T>()
    {
        //implement some error checking here
        Type tLogger = loggers[typeof(T)];

        ILogger<T> logger = (ILogger<T>) Activator.CreateInstance(tLogger);

        return logger;
    }
}

您只需AddLoggerProvider为您想要支持的每种类型调用 ,可以在运行时进行扩展,它确保您明确地将接口的实现添加到库而不是某个对象,因为 ,所以速度不是很快Activator,但是创建记录器不会无论如何可能是一个瓶颈。希望它看起来没问题。

用法:

// initialize somewhere
LoggerFactory.AddLoggerProvider<String, StringLogger>();
LoggerFactory.AddLoggerProvider<Exception, ExceptionLogger>();
// etc..

ILogger<string> stringLogger = LoggerFactory.CreateLogger<string>();

注意:每个都ILogger<T>需要一个无参数的构造函数Activator,但这也可以通过new()add 方法中的通用约束来确保。

于 2009-07-17T20:55:30.283 回答
6

我想我会这样做:

public class LoggerFactory<T>
{
    private static Dictionary<Type, Func<ILogger<T>>> LoggerMap = 
        new Dictionary<Type, Func<ILogger<T>>>
    {
        { typeof(string), 
            () => new StringILogger() as ILogger<T> },
        { typeof(StringWriter), 
            () => new StringWriterILogger() as ILogger<T> }
    };

    public static ILogger<T> CreateLogger()
    {
        return LoggerMap[typeof(T)]();
    }
}

你付出了可读性的代价(所有这些尖括号,哎呀),但正如你所看到的,它只需要很少的程序逻辑。

于 2009-07-17T20:12:31.807 回答
4

虽然我通常会推荐使用依赖注入框架,但您可以使用反射来实现一些东西,它会在可用类型中搜索实现适当 ILogger 接口的类型。

我建议您仔细考虑哪些程序集将包含这些记录器实现以及您希望解决方案的可扩展性和防弹性。跨可用程序集和类型执行运行时搜索并不便宜。然而,在这种类型的设计中,这是一种允许可扩展性的简单方法。它还避免了预先配置的问题——但是它要求只有一个具体类型实现 ILogger<> 接口的特定版本——否则你必须解决一个模棱两可的情况。

您可能希望执行一些内部缓存以避免在每次调用 Create() 时执行反射的开销。

这是您可以开始使用的一些示例代码。

using System;
using System.Linq;
using System.Reflection;

public interface ILogger<T> { /*... */}

public class IntLogger : ILogger<int> { }

public class StringLogger : ILogger<string> { }

public class DateTimeLogger : ILogger<DateTime> { }

public class LoggerFactory
{
    public static ILogger<T> Create<T>()
    {
        // look within the current assembly for matching implementation
        // this could be extended to search across all loaded assemblies
        // relatively easily - at the expense of performance
        // also, you probably want to cache these results...
        var loggerType = Assembly.GetExecutingAssembly()
                     .GetTypes()
                     // find implementations of ILogger<T> that match on T
                     .Where(t => typeof(ILogger<T>).IsAssignableFrom(t))
                     // throw an exception if more than one handler found,
                     // could be revised to be more friendly, or make a choice
                     // amongst multiple available options...
                     .Single(); 

        /* if you don't have LINQ, and need C# 2.0 compatibility, you can use this:
        Type loggerType;
        Type[] allTypes = Assembly.GetExecutingAssembly().GetTypes();
        foreach( var type in allTypes )
        {
            if( typeof(ILogger<T>).IsAssignableFrom(type) && loggerType == null )
                loggerType = type;
            else
                throw new ApplicationException( "Multiple types handle ILogger<" + typeof(T).Name + ">" );                   
        }

        */

        MethodInfo ctor = loggerType.GetConstructor( Type.EmptyTypes );
        if (ctor != null)
            return ctor.Invoke( null ) as ILogger<T>;

        // couldn't find an implementation
        throw new ArgumentException(
          "No mplementation of ILogger<{0}>" + typeof( T ) );
    }
}

// some very basic tests to validate the approach...
public static class TypeDispatch
{
    public static void Main( string[] args )
    {
        var intLogger      = LoggerFactory.Create<int>();
        var stringLogger   = LoggerFactory.Create<string>();
        var dateTimeLogger = LoggerFactory.Create<DateTime>();
        // no logger for this type; throws exception...
        var notFoundLogger = LoggerFactory.Create<double>(); 
    }
}
于 2009-07-17T19:29:58.383 回答
2

取决于您打算处理多少种类型。如果它很小(小于 10),我建议使用 switch 语句,因为它会更快更清晰地阅读。如果你想要更多,你会想要一个查找表(哈希映射、字典等),或者一些基于反射的系统。

于 2009-07-17T18:15:37.470 回答
1

switch 语句与字典 - 对性能无关紧要,因为 switch 被编译成字典。所以实际上这是一个可读性和灵活性的问题。该开关更易于阅读,另一方面,可以在运行时扩展字典。

于 2009-07-17T18:22:09.970 回答
1

您可能会考虑在此处使用Unity 之类的依赖注入框架。您可以使用您的因素将返回的通用类型对其进行配置,并在配置中进行映射。 这是一个例子

于 2009-07-17T18:39:59.420 回答
1

1) 我总是对人们投入日志的复杂性感到惊讶。对我来说似乎总是矫枉过正。如果 log4net 是开源的,我建议你去看看,事实上,你也可以使用它......

2) 就我个人而言,我尽量避免类型检查——它违背了泛型的意义。只需使用 .ToString() 方法即可完成。

于 2009-07-17T20:50:12.837 回答
0

嗯...您实际上可以尝试更聪明一点,这取决于给定的运行时系统支持什么。如果可以的话,我实际上会尽量避免在我的代码中使用任何条件语句,尤其是在多态和动态绑定的代码中。你在那里有一个通用类,那么为什么不使用它呢?

例如,在 Java 中,您可以特别利用已有的静态方法来执行以下操作:

public class LoggerFactory<T>
{
    public static ILogger<T> CreateLogger(Class<? extends SomeUsefulClass> aClass);
    {
        // where getLogger() is a class method SomeUsefulClass and its subclasses
        // and has a return value of Logger<aClass>.
        return aClass.getLogger();

        // Or perhaps you meant something like the below, which is also valid.
        // it passes the generic type to the specific class' getLogger() method
        // for correct instantiation. However, be careful; you don't want to get
        // in the habit of using generics as variables. There's a reason they're
        // two different things.

        // return aClass.getLogger(T);
    }
}

你会这样称呼它:

public static void main(String[] args)
{
    Logger = LoggerFactory.createLogger(subclassOfUsefulClass.class);
    // And off you go!
}

这避免了必须有任何条件并且更灵活:任何作为 SomeUsefulClass 的子类(或可能实现记录器接口)的类都可以返回正确类型的记录器实例。

于 2009-07-17T19:11:42.787 回答