Betrachten Sie dieses Szenario. Ich habe eine Geschäftslogik, die von Zeit zu Zeit in ein Protokoll geschrieben werden muss.
interface ILogger
{
void Log(string stuff);
}
interface IDependency
{
string GetInfo();
}
class MyBusinessObject
{
private IDependency _dependency;
public MyBusinessObject(IDependency dependency)
{
_dependency = dependency;
}
public string DoSomething(string input)
{
// Process input
var info = _dependency.GetInfo();
var intermediateResult = PerformInterestingStuff(input, info);
if (intermediateResult== "SomethingWeNeedToLog")
{
// How do I get to the ILogger-interface?
}
var result = PerformSomethingElse(intermediateResult);
return result;
}
}
Wie würden Sie die ILogger-Schnittstelle bekommen? Ich sehe zwei Hauptmöglichkeiten;
Welche Methode bevorzugen Sie und warum? Oder gibt es ein noch besseres Muster?
Update: Beachten Sie, dass ich nicht alle Methodenaufrufe protokollieren müssen. Ich möchte nur einige (seltene) Ereignisse protokollieren, die innerhalb meiner Methode auftreten können oder nicht.
Ich persönlich mache eine Mischung aus beidem.
Hier sind meine Konventionen:
Ich denke, das gibt mir die richtige Balance zwischen Testbarkeit. Ich finde es etwas schwieriger, Tests für Klassen einzurichten, die Service Location verwenden, als DI zu verwenden. Aus diesem Grund ist Service Location eher die Ausnahme als die Regel. Ich bin konsequent in seiner Verwendung, so ist es nicht schwer zu merken, welche Art von Test ich schreiben muss.
Einige haben die Befürchtung geäußert, dass DI Konstruktoren überfrachtet. Ich glaube nicht, dass dies ein Problem ist, aber wenn Sie so denken, gibt es eine Reihe von Alternativen, die DI verwenden, aber Konstruktorparameter vermeiden. Hier ist eine Liste der DI-Methoden von Ninject: http://ninject.codeplex.com/wikipage?title=Injection%20Patterns
Sie werden feststellen, dass die meisten Inversion of Control-Container dieselben Funktionen wie Ninject haben. Ich habe mich für Ninject entschieden, weil sie die prägnantesten Samples haben.
Hoffentlich ist das hilfreich.
Edit: Um es klar zu machen, verwende ich Unity und Common Service Locator . Ich habe eine Singleton-Instanz meines Unity-Containers für DI und meine Implementierung von IServiceLocator ist einfach ein Wrapper um diesen Singleton-Unity-Container. Auf diese Weise muss ich keine Typenzuordnungen zweimal oder so etwas machen.
Ich finde auch nicht, dass AOP über die Rückverfolgung hinaus besonders hilfreich ist. Ich mag manuelle Protokollierung einfach wegen seiner Klarheit besser. Ich weiß, dass die meisten AOP-Protokollierungsframeworks zu beidem in der Lage sind, aber ich benötige die meiste Zeit nicht das erste (AOP's bread and butter). Dies ist natürlich nur eine persönliche Präferenz.
Der Logger ist eindeutig ein Dienst, von dem Ihre Geschäftslogik abhängt, und sollte daher genauso wie IDependency
als Abhängigkeit behandelt werden. Injizieren Sie den Logger in Ihren Konstruktor.
Hinweis: Obwohl AOP als der Weg zur Protokollierung erwähnt wird, stimme ich nicht zu, dass es in diesem Fall die Lösung ist. AOP eignet sich hervorragend für die Ausführungsverfolgung, wird jedoch niemals eine Lösung für die Protokollierung als Teil der Geschäftslogik sein.
Meine kleine Faustregel:
Wenn es sich um eine Klassenbibliothek handelt, verwenden Sie entweder Konstruktorinjektion oder Eigenschaftsinjektion mit einem Nullobjektmuster.
Wenn es sich um eine Hauptanwendung handelt, verwenden Sie den Service Locator (oder Singleton).
Ich finde das trifft ziemlich gut zu, wenn ich log4net benutze. Sie möchten nicht, dass Klassenbibliotheken auf Dinge zugreifen, die möglicherweise nicht vorhanden sind, aber in einem Anwendungsprogramm wissen Sie, dass der Logger vorhanden sein wird, und Bibliotheken wie log4net basieren stark auf dem Service-Location-Muster.
Ich neige dazu, die Protokollierung als etwas ausreichend Statisches zu betrachten, das DI nicht wirklich benötigt. Es ist äußerst unwahrscheinlich, dass ich jemals die Protokollierungsimplementierung in einer Anwendung ändern werde, zumal jedes Protokollierungsframework unglaublich flexibel und einfach zu erweitern ist. In Klassenbibliotheken ist es wichtiger, wenn Ihre Bibliothek möglicherweise von mehreren Anwendungen verwendet werden muss, die bereits verschiedene Logger verwenden.
YMMV natürlich. DI ist großartig, aber das bedeutet nicht, dass alles DI'ed werden muss.
Wir haben alle unsere Logging/Tracing-Attribute auf PostSharp (AOP-Framework) umgestellt. Alles, was Sie tun müssen, um die Protokollierung für eine Methode zu erstellen, ist das Attribut hinzuzufügen.
Leistungen:
Check out this .
Sie könnten einen anderen Typ ableiten, z. LoggableBusinessObject
, die einen Logger in ihren Konstruktor aufnimmt. Das heißt, Sie übergeben den Logger nur für Objekte, die ihn verwenden:
public class MyBusinessObject
{
private IDependency _dependency;
public MyBusinessObject(IDependency dependency)
{
_dependency = dependency;
}
public virtual string DoSomething(string input)
{
// Process input
var info = _dependency.GetInfo();
var result = PerformInterestingStuff(input, info);
return result;
}
}
public class LoggableBusinessObject : MyBusinessObject
{
private ILogger _logger;
public LoggableBusinessObject(ILogger logger, IDependency dependency)
: base(dependency)
{
_logger = logger;
}
public override string DoSomething(string input)
{
string result = base.DoSomething(input);
if (result == "SomethingWeNeedToLog")
{
_logger.Log(result);
}
}
}
Vielleicht ist das ein wenig offtopisch, aber warum brauchen wir überhaupt das Injizieren von Loggern, wenn wir zu Beginn der Klasse einfach tippen können:
Logger logger = LogManager.GetLogger("MyClassName");
Der Logger ändert sich während der Entwicklung und später während der Wartung nicht. Moderne Logger sind hochgradig anpassbar, so das Argument
was ist, wenn ich den Textlogger durch eine Datenbank ersetzen möchte?
ist vermisst.
Ich negiere nicht mit Abhängigkeitsinjektion, ich bin nur neugierig auf deinen Verstand.
Ich würde Singleton Service bevorzugen.
Die Abhängigkeitsinjektion würde den Konstruktor überladen.
Wenn Sie AOP verwenden können, ist dies das Beste.
DI würde hier gut funktionieren. Eine andere Sache zu betrachten wäre AOP .
Ich würde keinen dieser Ansätze empfehlen. Besser die aspektorientierte Programmierung. Protokollierung ist die "Hallo Welt" von AOP.