Фабрика создания объектов в соответствии с универсальным типом C # - PullRequest
7 голосов
/ 17 июля 2009

Каков наиболее эффективный способ создания объекта в соответствии с универсальным типом, передаваемым классу Factory, например:

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();
    }
}

Как бы вы это сделали? Какой оператор ветвления ? и т.д ...

Ответы [ 8 ]

18 голосов
/ 18 июля 2009

Я думаю, что лучше сохранить это простым, возможно, что-то вроде этого:

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.

6 голосов
/ 18 июля 2009

Я думаю, я бы сделал это так:

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)]();
    }
}

Вы платите что-то по цене читабельности (все эти угловые скобки, хе-хе), но, как вы можете видеть, это приводит к очень маленькой логике программы.

4 голосов
/ 17 июля 2009

Хотя я бы обычно рекомендовал использовать инфраструктуру внедрения зависимостей, вы могли бы реализовать что-то с отражением, которое будет искать доступные типы для типа, реализующего соответствующий интерфейс 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>(); 
    }
}
2 голосов
/ 17 июля 2009

Зависит от того, сколько типов вы собираетесь обрабатывать. Если оно маленькое (меньше 10), я бы предложил оператор switch, так как он будет быстрее и чище читать. Если вы хотите больше, вам нужна таблица поиска (Hash Map, Dictionary и т. Д.) Или какая-либо система, основанная на отражениях.

1 голос
/ 18 июля 2009

1) Я всегда поражаюсь сложности, которую люди вносят в логирование. Всегда кажется излишним для меня. Если log4net с открытым исходным кодом, я бы порекомендовал вам взглянуть на это, на самом деле, вы могли бы также использовать его ...

2) Лично я стараюсь по возможности избегать проверки типов - это побеждает точку обобщений. Просто используйте метод .ToString () и покончите с этим.

1 голос
/ 17 июля 2009

Вы можете рассмотреть возможность использования инфраструктуры внедрения зависимостей, например Unity . Вы можете настроить его с помощью универсальных типов, которые будет возвращать ваш фактор, и выполнять сопоставление в конфигурации. Вот пример этого .

1 голос
/ 17 июля 2009

оператор переключателя против словаря - не имеет значения для производительности, так как ключ переключается в словарь. Так что на самом деле это вопрос читабельности и гибкости. Переключатель легче читать, с другой стороны, словарь может быть расширен во время выполнения.

0 голосов
/ 17 июля 2009

Хм ... на самом деле вы можете попытаться быть немного более умным в этом вопросе, в зависимости от того, что поддерживает данная система времени выполнения. Я действительно стараюсь избегать любых условных операторов в моем коде, если могу, особенно в полиморфном и динамически связанном коде. У вас есть общий класс, так почему бы не использовать его?

Например, в 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, может возвращать правильно типизированный экземпляр средства ведения журнала.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...