Logger sind das, was wir als "Querschnittsthema" bezeichnen. Sie geben Techniken wie aspektorientiertes Programmieren nach; Wenn Sie eine Möglichkeit haben, Ihre Klassen mit einem Attribut zu dekorieren oder Code zu weben, ist dies eine gute Möglichkeit, Protokollierungsfunktionen zu erhalten und gleichzeitig Ihre Objekte und Parameterlisten "rein" zu halten.
Der einzige Grund, warum Sie einen Logger möglicherweise übergeben möchten, ist, wenn Sie verschiedene Logging-Implementierungen angeben möchten, die meisten Logging-Frameworks jedoch so flexibel sind, dass Sie sie beispielsweise für verschiedene Logging-Ziele konfigurieren können. (Protokolldatei, Windows Event Manager usw.)
Aus diesen Gründen ziehe ich es vor, die Protokollierung zu einem natürlichen Teil des Systems zu machen, anstatt einen Protokollierer zu Protokollierungszwecken an jede Klasse zu übergeben. Im Allgemeinen verweise ich also auf den entsprechenden Protokollierungsnamespace und verwende einfach den Logger in meinen Klassen.
Wenn Sie dennoch einen Logger übergeben möchten, ist es meine Präferenz, dass Sie ihn zum letzten Parameter in der Parameterliste machen (machen Sie ihn, wenn möglich, zu einem optionalen Parameter). Es macht wenig Sinn, wenn es der erste Parameter ist. Der erste Parameter sollte der wichtigste sein, der für die Operation der Klasse am relevantesten ist.
In Sprachen mit Funktionsüberladung würde ich argumentieren, dass je wahrscheinlicher ein Argument optional ist, desto genauer sollte es sein. Dies schafft Konsistenz, wenn Sie Überladungen dort erstellen, wo sie fehlen:
In funktionalen Sprachen ist das Umgekehrte sinnvoller - je wahrscheinlicher Sie eine Standardeinstellung wählen, desto weiter links sollte sie sein. Dies erleichtert die Spezialisierung der Funktion durch einfaches Anwenden von Argumenten:
Wie in den anderen Antworten erwähnt, möchten Sie den Logger jedoch wahrscheinlich nicht explizit in der Argumentliste jeder Klasse im System übergeben.
quelle
Sie können definitiv viel Zeit damit verbringen, dieses Problem zu überarbeiten.
Für Sprachen mit kanonischen Protokollierungsimplementierungen müssen Sie den kanonischen Protokollierer nur direkt in jeder Klasse instanziieren.
Versuchen Sie bei Sprachen ohne kanonische Implementierung, ein Protokollierungs-Fassaden-Framework zu finden und sich daran zu halten. slf4j ist eine gute Wahl in Java.
Persönlich würde ich mich lieber an eine einzelne konkrete Protokollierungsimplementierung halten und alles an syslog senden. Alle guten Tools zur Protokollanalyse können Sysout-Protokolle von mehreren App-Servern zu einem umfassenden Bericht zusammenfassen.
Wenn eine Funktionssignatur einen oder zwei Abhängigkeitsdienste sowie einige "echte" Argumente enthält, platziere ich die Abhängigkeiten zuletzt:
int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)
Da meine Systeme in der Regel nur fünf oder weniger solcher Dienste enthalten, stelle ich immer sicher, dass die Dienste über alle Funktionssignaturen hinweg in derselben Reihenfolge enthalten sind. Die alphabetische Reihenfolge ist so gut wie jede andere. (Abgesehen davon: Wenn Sie diesen methodischen Ansatz für den Umgang mit Mutex beibehalten, verringern Sie auch die Wahrscheinlichkeit, dass Deadlocks auftreten.)
Wenn Sie feststellen, dass Sie mehr als ein Dutzend Abhängigkeiten in Ihre App einschleusen, muss das System wahrscheinlich in separate Subsysteme aufgeteilt werden (kann ich sagen, Microservices?).
quelle
Logger sind etwas Besonderes, weil sie buchstäblich überall verfügbar sein müssen.
Wenn Sie entschieden haben, dass Sie einen Logger an den Konstruktor jeder Klasse übergeben möchten, sollten Sie auf jeden Fall eine einheitliche Konvention festlegen (z. B. immer der erste Parameter, der immer als Referenz übergeben wird, die Konstruktorinitialisierungsliste beginnt immer mit m_logger (theLogger), etc). Alles, was in Ihrer gesamten Codebasis verwendet wird, wird eines Tages von Konsistenz profitieren.
Alternativ könnte jede Klasse ihr eigenes Logger-Objekt instanziieren, ohne dass etwas übergeben werden muss. Der Logger muss möglicherweise ein paar Dinge "magisch" wissen, damit dies funktioniert, aber das Hardcodieren eines Dateipfads in der Klassendefinition ist möglicherweise eine viel wartbarer und weniger mühsam, als es richtig an Hunderte verschiedener Klassen weiterzugeben, und viel weniger schlimm, als eine globale Variable zu verwenden, um diese Langeweile zu umgehen. (Zugegeben, Logger gehören zu den wenigen legitimen Anwendungsfällen für globale Variablen.)
quelle
Ich stimme denen zu, die vorschlagen, dass auf den Logger statisch zugegriffen und nicht in Klassen weitergegeben werden soll. Allerdings , wenn es ein starker Grund dafür ist , wollen Sie sie passieren in (vielleicht verschiedene Instanzen zu verschiedenen Orten oder etwas protokollieren wollen) , dann würde ich vorschlagen , Sie es nicht passieren den Konstruktor , sondern über einen separaten Anruf , dies zu tun machen, zum Beispiel
Class* C = new C(); C->SetLogger(logger);
eher alsClass* C = new C(logger);
Der Grund für den Vorzug dieser Methode liegt darin, dass der Logger kein wesentlicher Bestandteil der Klasse ist, sondern vielmehr ein injiziertes Feature, das für einen anderen Zweck verwendet wird. Wenn Sie es in die Konstruktorliste aufnehmen, wird es zu einer Anforderung der Klasse und impliziert, dass es Teil des tatsächlichen logischen Status der Klasse ist. Es ist vernünftig zu erwarten, zum Beispiel (mit dem meisten Klassen , obwohl nicht alle), dass , wenn
X != Y
dannC(X) != C(Y)
aber es ist unwahrscheinlich , dass Sie Logger Ungleichheit testen würden , wenn Sie sind zu Instanzen derselben Klasse zu vergleichen.quelle
Erwähnenswert ist, dass ich bisher noch nicht gesehen habe, wie die anderen Antworten hier angesprochen wurden: Indem der Logger über eine Eigenschaft oder statisch injiziert wird, wird es schwierig, die Klasse einem Komponententest zu unterziehen. Wenn Sie Ihren Logger beispielsweise über die Eigenschaft injizieren, müssen Sie diesen Logger jetzt jedes Mal injizieren, wenn Sie eine Methode testen, die den Logger verwendet. Dies bedeutet, dass Sie es genauso gut als Konstruktorabhängigkeit festlegen können, da die Klasse es erfordert.
Static bietet sich für das gleiche Problem an; Wenn der Logger nicht funktioniert, schlägt Ihre gesamte Klasse fehl (wenn Ihre Klasse den Logger verwendet) - obwohl der Logger nicht unbedingt Teil der Verantwortung der Klasse ist - obwohl dies nicht annähernd so schlimm ist wie die Eigenschaftsinjektion, weil Sie Zumindest wissen, dass der Logger in gewissem Sinne immer "da" ist.
Nur ein paar Denkanstöße, besonders wenn Sie TDD einsetzen. Meiner Meinung nach sollte ein Logger wirklich nicht Teil eines prüfbaren Teils einer Klasse sein (wenn Sie die Klasse testen, sollten Sie auch nicht Ihre Protokollierung testen).
quelle
Ich bin zu faul, um ein Logger-Objekt an jede Klasseninstanz weiterzugeben. In meinem Code befinden sich diese Dinge entweder in einem statischen Feld oder in einer threadlokalen Variablen in einem statischen Feld. Letzteres ist ziemlich cool und ermöglicht die Verwendung eines anderen Protokollierers für jeden Thread sowie das Hinzufügen von Methoden zum Ein- und Ausschalten der Protokollierung, die in einer Multithread-Anwendung eine sinnvolle und erwartete Funktion haben.
quelle