Ответ 1
Раньше я использовал логические фасады, такие как Common.Logging (даже чтобы скрыть свой собственный CuttingEdge.Logging), но в настоящее время я использую шаблон инъекции зависимостей, и это позволяет мне скрыть регистраторы за моей (простая) абстракция, которая придерживается как Принцип инверсии зависимостей, так и Разделение интерфейса Принцип (ISP), поскольку он имеет один член и потому, что интерфейс определен моим приложением; а не внешняя библиотека. Минимизируя знание о том, что основные части вашего приложения имеют о существовании внешних библиотек, тем лучше; даже если у вас нет намерения когда-либо заменять вашу регистрационную библиотеку. Жесткая зависимость от внешней библиотеки усложняет проверку вашего кода и усложняет ваше приложение API, который никогда не был разработан специально для вашего приложения.
Это то, что абстракция часто выглядит в моих приложениях:
public interface ILogger
{
void Log(LogEntry entry);
}
public enum LoggingEventType { Debug, Information, Warning, Error, Fatal };
// Immutable DTO that contains the log information.
public class LogEntry
{
public readonly LoggingEventType Severity;
public readonly string Message;
public readonly Exception Exception;
public LogEntry(LoggingEventType severity, string message, Exception exception = null)
{
if (message == null) throw new ArgumentNullException("message");
if (message == string.Empty) throw new ArgumentException("empty", "message");
this.Severity = severity;
this.Message = message;
this.Exception = exception;
}
}
Необязательно, эту абстракцию можно расширить с помощью некоторых простых методов расширения (позволяя интерфейсу оставаться узким и придерживаться ISP). Это делает код для пользователей этого интерфейса намного проще:
public static class LoggerExtensions
{
public static void Log(this ILogger logger, string message) {
logger.Log(new LogEntry(LoggingEventType.Information, message));
}
public static void Log(this ILogger logger, Exception exception) {
logger.Log(new LogEntry(LoggingEventType.Error, exception.Message, exception));
}
// More methods here.
}
Поскольку интерфейс содержит только один метод, вы можете легко создать реализацию ILogger
, которая прокси для log4net, в Serilog, Microsoft.Extensions.Logging, NLog или любую другую библиотеку протоколирования и сконфигурируйте свой контейнер DI, чтобы ввести его в классы с ILogger
в их конструктор.
Обратите внимание, что наличие статических методов расширения поверх интерфейса с помощью одного метода существенно отличается от интерфейса со многими членами. Методы расширения - это просто вспомогательные методы, которые создают сообщение LogEntry
и передают его через единственный метод в интерфейсе ILogger
. Методы расширения становятся частью потребительского кода; а не частью абстракции. Мало того, что это позволяет развивать методы расширения без необходимости изменения абстракции, методы расширения и конструктор LogEntry
всегда выполняются, когда используется абстракция журнала, даже если этот логгер заштрихован/издевается. Это дает больше уверенности в правильности запросов к журналу при запуске в тестовом наборе. Одночленный интерфейс упрощает тестирование; Наличие абстракции со многими участниками затрудняет создание реализаций (таких как макеты, адаптеры и декораторы).
Когда вы это делаете, вряд ли когда-либо понадобится какая-то статическая абстракция, которую могут предложить логические фасады (или любая другая библиотека).