Früher habe ich Protokollierungsfassaden wie Common.Logging verwendet (sogar um meine eigene CuttingEdge.Logging- Bibliothek auszublenden ), aber heute verwende ich das Abhängigkeitsinjektionsmuster. Dadurch kann ich Protokollierer hinter meiner eigenen (einfachen) Abstraktion ausblenden, die beiden Abhängigkeiten entspricht Inversionsprinzip und das Prinzip der Schnittstellentrennung(ISP), weil es ein Mitglied hat und weil die Schnittstelle von meiner Anwendung definiert wird; keine externe Bibliothek. Je besser das Wissen ist, das die Kernteile Ihrer Anwendung über die Existenz externer Bibliotheken haben, desto besser. auch wenn Sie nicht beabsichtigen, Ihre Protokollierungsbibliothek jemals zu ersetzen. Die starke Abhängigkeit von der externen Bibliothek erschwert das Testen Ihres Codes und erschwert Ihre Anwendung mit einer API, die nie speziell für Ihre Anwendung entwickelt wurde.
So sieht die Abstraktion in meinen Anwendungen oft aus:
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;
}
}
Optional kann diese Abstraktion mit einigen einfachen Erweiterungsmethoden erweitert werden (so dass die Schnittstelle eng bleibt und weiterhin am ISP festhält). Dies macht den Code für die Benutzer dieser Schnittstelle viel einfacher:
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.
}
Da die Schnittstelle nur eine einzige Methode enthält, können Sie problemlos eine ILogger
Implementierung erstellen, die als Proxy für log4net fungiert , zu Serilog , Microsoft.Extensions.Logging , NLog oder jede andere Logging - Bibliothek und Ihre DI - Container konfigurieren es in Klassen zu injizieren , die eine haben ILogger
in ihren Konstrukteur.
Beachten Sie, dass sich statische Erweiterungsmethoden über einer Schnittstelle mit einer einzelnen Methode erheblich von einer Schnittstelle mit vielen Mitgliedern unterscheiden. Die Erweiterungsmethoden sind nur Hilfsmethoden, die eine LogEntry
Nachricht erstellen und über die einzige Methode auf der ILogger
Schnittstelle weiterleiten. Die Erweiterungsmethoden werden Teil des Verbrauchercodes. nicht Teil der Abstraktion. Dies ermöglicht nicht nur die Weiterentwicklung der Erweiterungsmethoden, ohne dass die Abstraktion, die Erweiterungsmethoden und die geändert werden müssenLogEntry
Konstruktoren werden immer ausgeführt, wenn die Logger-Abstraktion verwendet wird, auch wenn dieser Logger gestubbt / verspottet ist. Dies gibt mehr Sicherheit über die Richtigkeit der Aufrufe des Loggers, wenn er in einer Testsuite ausgeführt wird. Die eingliedrige Oberfläche erleichtert auch das Testen erheblich. Eine Abstraktion mit vielen Mitgliedern macht es schwierig, Implementierungen (wie Mocks, Adapter und Dekoratoren) zu erstellen.
Wenn Sie dies tun, ist kaum eine statische Abstraktion erforderlich, die Protokollierungsfassaden (oder eine andere Bibliothek) bieten könnten.