Является ли хорошей практикой иметь регистратор как синглтон? - PullRequest
75 голосов
/ 12 декабря 2011

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

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

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

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

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

    Infrastructure.Logger.Info("blabla");

ОБНОВЛЕНИЕ: Как правильно заметил Мерлин, я должен упомянуть, что в первом и втором примерах я использую DI.

Ответы [ 8 ]

37 голосов
/ 12 декабря 2011

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

34 голосов
/ 13 декабря 2011

Это тоже раздражает - это не СУХОЙ

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

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

Singleton

Одиночки ужасны <flame-suit-on>.

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

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

Если вы сделаете это, проверьте ответы других людей, используя шаблон Registry (см. Описание), и тех, кто регистрирует (переустанавливаемый) синглтон фабрика , а не синглтон Экземпляр регистратора.

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

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

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

Использование AOP для СУШКИ

Вы могли бы немного убрать этот код внедрения свойства, используя среду Aspect Oriented Programming (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
23 голосов
/ 12 декабря 2011

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

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

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

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

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

Registry.Logger.Info("blabla");
9 голосов
/ 13 декабря 2011

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

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

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 без изменения какого-либо кода (и, следовательно, в соответствии с принципом Open / Closed).

Пример расширения

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!");
    }
}
3 голосов
/ 13 декабря 2011

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

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

Вот одна из причин использования этой диаграммы:

  1. У вас есть зависимость или она нужна? - нужно
  2. Это сквозная проблема? - Да
  3. Вам нужен ответ от него? - Нет

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

0 голосов
/ 28 декабря 2015

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

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
0 голосов
/ 13 декабря 2011

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

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

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

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

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

...