Kiedyś używałem fasad rejestrowania, takich jak Common.Logging (nawet w celu ukrycia mojej własnej biblioteki CuttingEdge.Logging ), ale obecnie używam wzorca Dependency Injection, co pozwala mi ukryć rejestratory za własną (prostą) abstrakcją, która jest zgodna z obydwoma Zależnościami Zasada inwersji i zasada segregacji interfejsów(ISP), ponieważ ma jednego członka i ponieważ interfejs jest zdefiniowany przez moją aplikację; nie jest to biblioteka zewnętrzna. Im mniej wiedzy na temat istnienia bibliotek zewnętrznych, które mają podstawowe części aplikacji, tym lepiej; nawet jeśli nie masz zamiaru kiedykolwiek zastępować swojej biblioteki rejestrowania. Twarda zależność od biblioteki zewnętrznej utrudnia testowanie kodu i komplikuje aplikację za pomocą interfejsu API, który nigdy nie został zaprojektowany specjalnie dla tej aplikacji.
Tak często wygląda abstrakcja w moich aplikacjach:
public interface ILogger
{
void Log(LogEntry entry);
}
public enum LoggingEventType { Debug, Information, Warning, Error, Fatal };
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;
}
}
Opcjonalnie tę abstrakcję można rozszerzyć za pomocą kilku prostych metod rozszerzających (pozwalając interfejsowi pozostać wąskim i zachować zgodność z dostawcą usług internetowych). To sprawia, że kod dla użytkowników tego interfejsu jest znacznie prostszy:
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));
}
}
Ponieważ interfejs zawiera tylko jedną metodę, możesz łatwo utworzyć ILogger
implementację, która będzie pośredniczyła w log4net , Serilog , Microsoft.Extensions.Logging , NLog lub dowolnej innej bibliotece rejestrowania i skonfiguruj swój kontener DI tak, aby wstrzykiwał go w klasach, które mają ILogger
w swoim konstruktor.
Zwróć uwagę, że posiadanie statycznych metod rozszerzających na interfejsie z pojedynczą metodą różni się od posiadania interfejsu z wieloma składowymi. Metody rozszerzające to tylko metody pomocnicze, które tworzą LogEntry
wiadomość i przekazują ją przez jedyną metodę w ILogger
interfejsie. Metody rozszerzające stają się częścią kodu konsumenta; nie jest częścią abstrakcji. Nie tylko umożliwia to ewolucję metod rozszerzających bez konieczności zmiany abstrakcji, metod rozszerzających i rozszerzeniaLogEntry
konstruktor są zawsze wykonywane, gdy używana jest abstrakcja rejestratora, nawet jeśli ten rejestrator jest ukryty / mockowany. Daje to większą pewność co do poprawności wywołań programu rejestrującego podczas uruchamiania w zestawie testów. Jednoczłonowy interfejs również znacznie ułatwia testowanie; Posiadanie abstrakcji z wieloma członkami utrudnia tworzenie implementacji (takich jak makiety, adaptery i dekoratory).
Kiedy to robisz, prawie nigdy nie ma potrzeby stosowania statycznej abstrakcji, jaką mogą oferować fasady logowania (lub jakakolwiek inna biblioteka).