Это хорошая практика, чтобы иметь logger в качестве синглтона?


у меня была привычка передавать логгер конструктору, например:

public class OrderService : IOrderService {
     public OrderService(ILogger logger) {
     }
}

но это довольно раздражает, поэтому я использовал это свойство в течение некоторого времени:

private ILogger logger = NullLogger.Instance;
public ILogger Logger
{
    get { return logger; }
    set { logger = value; }
}

это тоже раздражает - это не сухо, мне нужно повторить это в каждом классе. Я мог бы использовать базовый класс, но опять же - я использую класс формы, поэтому понадобится FormBase и т. д. Поэтому я думаю, что было бы недостатком того, что синглтон с ILogger разоблачен, поэтому veryone будет знать, где получить лесоруб:

    Infrastructure.Logger.Info("blabla");

UPDATE: Как правильно заметил Мерлин, я должен упомянуть, что в первом и втором примерах я использую DI.

8 71

8 ответов:

это тоже раздражает-это не сухой

это правда. Но есть только то, что вы можете сделать для сквозного беспокойства, которое пронизывает каждый тип, который у вас есть. Вы должны использовать регистратор везде, поэтому вы должны иметь свойство на этих типах.

Итак, давайте посмотрим, что мы можем сделать об этом.

Синглтон

синглтоны-это ужасно <flame-suit-on>.

Я рекомендую вставлять с помощью инъекции свойств, как вы сделали со своим вторым примером. Это лучший факторинг, который вы можете сделать, не прибегая к магии. Лучше иметь явную зависимость, чем скрывать ее через синглтон.

но если синглтоны сэкономить вам значительное время, в том числе все рефакторинга вам когда-нибудь придется делать (хрустальный шар время!), Я полагаю, вы могли бы жить с ними. Если когда-либо и было применение для Синглтона, то это может быть он. Имейте в виду, стоимость если вы когда-нибудь хотите изменить свое мнение будет примерно так высоко, как он получает.

если вы сделаете это, проверьте ответы других людей с помощью the Registry pattern (см. Описание), и те, кто регистрирует (resetable) singleton завод а не экземпляр одноэлементного регистратора.

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

Visual Studio фрагменты кода

вы могли бы использовать фрагменты кода Visual Studio чтобы ускорить ввод этого повторяющегося кода. Вы сможете ввести что-то вроде logger tab, и код волшебным образом появится для вас.

используя AOP, чтобы высохнуть

вы можете устранить немного этого кода инъекции свойств с помощью структура аспектно-ориентированного программирования (AOP), такая как PostSharp для автоматического создания некоторых из оно.

это может выглядеть примерно так, когда вы закончите:

[InjectedLogger]
public ILogger Logger { get; set; }

вы также можете использовать их метод трассировки пример кода для автоматического отслеживания кода входа и выхода метода, что может устранить необходимость добавления некоторых свойств регистратора вместе. Вы можете применить атрибут на уровне класса или пространства имен:

[Trace]
public class MyClass
{
    // ...
}

// or

#if DEBUG
[assembly: Trace( AttributeTargetTypes = "MyNamespace.*",
    AttributeTargetTypeAttributes = MulticastAttributes.Public,
    AttributeTargetMemberAttributes = MulticastAttributes.Public )]
#endif

я помещаю экземпляр logger в свой контейнер для инъекций зависимостей, который затем вводит logger в классы, которым он нужен.

хороший вопрос. Я считаю, что в большинстве проектов logger является синглтоном.

некоторые идеи просто приходят в голову:

  • использовать ServiceLocator (или Инъекции Зависимостей контейнер, если вы уже используете какой-либо), который позволяет вам совместно использовать logger через службы / классы, таким образом, вы можете создать экземпляр logger или даже несколько разных регистраторов и поделиться через ServiceLocator, который, очевидно, будет синглтоном, своего рода инверсия управления. Этот подход дает вам большую гибкость по сравнению с процессом создания и инициализации экземпляра регистратора.
  • Если вам нужен регистратор почти везде-реализуйте методы расширения для Object введите, чтобы каждый класс мог вызывать методы logger, такие как LogInfo(),LogDebug(),LogError()

синглтон-это хорошая идея. Еще лучше использовать реестр шаблон, который дает немного больше контроля над инстанцирования. На мой взгляд, одноэлементный шаблон слишком близок к глобальным переменным. При создании или повторном использовании объекта обработки реестра есть место для будущих изменений правил создания экземпляров.

сам реестр может быть статическим классом, чтобы дать простой синтаксис для доступа к журналу:

Registry.Logger.Info("blabla");

обычный синглтон-это не очень хорошая идея. Это затрудняет замену регистратора. Я обычно использую фильтры для своих регистраторов (некоторые "шумные" классы могут регистрировать только предупреждения/ошибки).

Я использую одноэлементный шаблон в сочетании с прокси-шаблоном для фабрики регистратора:

public class LogFactory
{
    private static LogFactory _instance;

    public static void Assign(LogFactory instance)
    {
        _instance = instance;
    }

    public static LogFactory Instance
    {
        get { _instance ?? (_instance = new LogFactory()); }
    }

    public virtual ILogger GetLogger<T>()
    {
        return new SystemDebugLogger();
    }
}

это позволяет мне создать FilteringLogFactory или просто SimpleFileLogFactory без изменения какого-либо кода (и, следовательно, соблюдение принципа открытия/закрытия).

образец расширение

public class FilteredLogFactory : LogFactory
{
    public override ILogger GetLogger<T>()
    {
        if (typeof(ITextParser).IsAssignableFrom(typeof(T)))
            return new FilteredLogger(typeof(T));

        return new FileLogger(@"C:\Logs\MyApp.log");
    }
}

и использовать новую фабрику

// and to use the new log factory (somewhere early in the application):
LogFactory.Assign(new FilteredLogFactory());

в вашем классе, который должен войти:

public class MyUserService : IUserService
{
    ILogger _logger = LogFactory.Instance.GetLogger<MyUserService>();

    public void SomeMethod()
    {
        _logger.Debug("Welcome world!");
    }
}

существует инъекция зависимости книги в. NET. на основе того, что вам нужно, вы должны использовать перехват.

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

вот как можно рассуждать, используя эту диаграмму:

  1. есть ли у вас зависимость или она вам нужна? - Нужно это
  2. это сквозное беспокойство? - Да
  3. вам нужен ответ от него? - Нет

Использовать Перехват

Если вы хотите посмотреть на хорошее решение для ведения журнала, я предлагаю вам посмотреть на Google App engine с python, где ведение журнала так же просто, как import logging и тогда вы можете просто logging.debug("my message") или logging.info("my message") что действительно держит его так просто, как это должно.

Java не было хорошего решения для ведения журнала ie log4j следует избегать, так как это практически заставляет вас использовать синглтоны, которые, как ответили здесь, "ужасны", и у меня был ужасный опыт с попыткой сделать вывод журнала таким же оператор logging только один раз, когда я подозреваю, что причиной двойного ведения журнала было то, что у меня есть один Синглтон объекта logging в двух загрузчиках классов в одной виртуальной машине(!)

Я прошу прощения за то, что не так конкретно в C#, но из того что я видел решения с C# похож на Java, где мы log4j и мы должны сделать его синглтоном.

вот почему мне очень понравился решение с GAE / python, это так просто, как это может быть и вам не нужно беспокоиться о загрузчиках классов, получении инструкции double logging или любого Шаблона дизайна вообще.

Я надеюсь, что некоторые из этой информации могут быть актуальны для вас, и я надеюсь, что вы хотите взглянуть на I logging решение я рекомендую вместо этого я запугиваю вниз на сколько проблема Синглтон заподозрить из-за невозможности иметь реальный синглтон, когда он должен быть instanciating в нескольких загрузчиков классов.

другое решение, которое я лично считаю самым простым, - использовать статический класс регистратора. Вы можете вызвать его из любого метода класса без необходимости изменять класс, например, добавить инъекцию свойств и т. д. Его довольно простой и легкий в использовании.

Logger::initialize ("filename.log", Logger::LEVEL_ERROR); // only need to be called once in your application

Logger::log ("my error message", Logger::LEVEL_ERROR); // to be used in every method where needed