Раньше я использовал каркасные фасады, такие как 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
всегда выполняются, когда используется абстракция регистратора, даже когда этот регистратор заглушен / осмеян. Это дает больше уверенности в правильности обращений к регистратору при запуске в тестовом наборе. Одночленный интерфейс также значительно облегчает тестирование; Наличие абстракции со многими членами затрудняет создание реализаций (таких как макеты, адаптеры и декораторы).
Когда вы делаете это, вряд ли когда-нибудь понадобится какая-то статическая абстракция, которую могут предложить фасады для каротажа (или любая другая библиотека).