Entwurfsmuster zum Umschließen der Ausführung mit Protokollierung

11

Einführung

Ich implementiere eine abstrakte Java-Klasse eines Verarbeitungsframeworks *. Durch die Implementierung der Funktion executekann ich geschäftslogische Funktionen hinzufügen. Ich möchte die Protokollierung zu Beginn und am Ende jeder Implementierung jeder meiner Ausführungsfunktionen hinzufügen. Auch zwischen einigen Protokollierungen, wenn bestimmte Dinge erledigt werden. Ich möchte dies jedoch einheitlich gestalten. Mein Gedanke war, die Framework-Klasse an eine eigene neue Klasse zu erben, die die Protokollierung implementiert und einige neue executeWithLoggingFunktionen bereitstellt , die die Protokollierung um die spezifischen Teile wickeln. Ich bin mir nicht sicher, ob das die beste Idee ist und ob ich ein Designmuster verwenden kann, das das ganze Unternehmen elegant macht. Wie würde ich mit der ganzen Sache weitermachen?

Herausforderungen (bitte beachten Sie diese!)

  1. Eine Herausforderung in meinem Fall ist, dass die ursprüngliche Klasse nur eine executeFunktion hat, ich jedoch mehrere Teile protokollieren müsste.
  2. Die zweite Sache ist: Unter Verwendung eines Decorator - Muster (war meine erste Idee auch) nicht ganz funktioniert, wenn ich auch benutze execute, weil die super.execute()-function in der ersten Zeile meines neuen genannt werden müssten execute(), ist es nicht ?
  3. Es wären mindestens 4 Klassen beteiligt: BaseFunctionvom Rahmen, LoggingFunction extends BaseFunctionvon mir, MyBusinessFunction extends LoggingFunctionvon mir, MyBusinessClassder instanziiert MyBusinessFunction.
  4. Ich muss mich nicht nur am Anfang und am Ende von execute, sondern auch in der Mitte anmelden.
  5. Die "Protokollierung" ist nicht nur eine einfache Java-Protokollierung, sondern eine Protokollierung in einer Datenbank. Dies ändert nichts an den Prinzipien, zeigt jedoch, dass die Protokollierung mehr als nur eine Codezeile sein kann.

Vielleicht wäre ein Beispiel, wie ich das Ganze machen würde, schön, um mich zum Laufen zu bringen.

| * Eine Storm Trident-Funktion, ähnlich einem Storm-Bolzen, aber dies ist nicht besonders wichtig.

Make42
quelle
1
super.execute wird vom Dekorateur aufgerufen, nicht vom Dekorateur. Ihre Hauptklasse sollte nicht einmal bemerken, dass es dekoriert wird, und Sie müssen nur vorsichtig mit diesen Aufrufen sein, da sie den Dekorateur nicht einschließen können (im Gegensatz zu einem Ansatz mit Mixins oder Merkmalen).
Frank
Ist executein BaseFunctionabstrakt?
Spotted
@Spotted: Ja, executeist abstrakt in BaseFunction.
Make42

Antworten:

13

Im Allgemeinen gibt es mehrere Möglichkeiten, eine bestimmte Ausführung zu protokollieren. Zunächst ist es gut, dass Sie dies trennen möchten, da die Protokollierung ein typisches Beispiel für die Trennung von Bedenken ist . Es macht wirklich wenig Sinn, die Protokollierung direkt in Ihre Geschäftslogik aufzunehmen.

In Bezug auf mögliche Entwurfsmuster ist hier eine (nicht schlüssige) Liste von oben:

  • Dekorationsmuster : Ein bekanntes Entwurfsmuster, bei dem Sie die nicht protokollierende Klasse grundsätzlich in eine andere Klasse derselben Schnittstelle einbinden und der Wrapper die Protokollierung vor und / oder nach dem Aufruf der umschlossenen Klasse durchführt.

  • Funktionszusammensetzung : In einer funktionalen Programmiersprache können Sie Ihre Funktion einfach mit einer Protokollierungsfunktion zusammenstellen. Dies ähnelt dem Dekorationsmuster in OO-Sprachen, ist jedoch keine bevorzugte Methode, da die zusammengesetzte Protokollierungsfunktion auf einer nebenwirkenden Implementierung basieren würde.

  • Monaden : Monaden stammen ebenfalls aus der Welt der funktionalen Programmierung und ermöglichen es Ihnen, Ihre Ausführung und Protokollierung zu entkoppeln. Dies bedeutet im Grunde, dass Sie die erforderlichen Protokollierungsnachrichten und Ausführungsmethoden zusammenfassen, aber auch noch nicht ausführen. Sie können dann die Monade verarbeiten, um das Schreiben von Protokollen und / oder die tatsächliche Ausführung der Geschäftslogik durchzuführen.

  • Aspektorientierte Programmierung : Anspruchsvollerer Ansatz, der eine vollständige Trennung erreicht, da die Klasse, die das Nichtprotokollierungsverhalten ausführt, niemals direkt mit der Protokollierung interagiert.

  • Weiterleitung: Möglicherweise sind auch andere Standardentwurfsmuster geeignet. Beispielsweise kann eine Fassade als protokollierter Einstiegspunkt dienen, bevor der Anruf an eine andere Klasse weitergeleitet wird, die den Geschäftslogikteil ausführt. Dies kann auch auf verschiedene Abstraktionsebenen angewendet werden. Beispielsweise können Sie Anforderungen an eine extern erreichbare Dienst-URL protokollieren, bevor Sie sie an eine interne URL weiterleiten, um sie tatsächlich nicht protokolliert zu verarbeiten.

Was Ihre zusätzliche Anforderung betrifft, dass Sie die Protokollierung "in der Mitte" durchführen müssen, deutet dies wahrscheinlich auf ein seltsames Design hin. Warum tut Ihre Ausführung so viel, dass so etwas wie eine "Mitte" ihrer Ausführung auch nur aus der Ferne von Interesse ist? Wenn tatsächlich so viel los ist, warum nicht in Phasen / Phasen / Was-hast-du? Theoretisch könnte man mit AOP eine Protokollierung in der Mitte erreichen, aber ich würde trotzdem argumentieren, dass eine Designänderung viel angemessener erscheint.

Frank
quelle
3

Ich habe das Gefühl, dass Sie unbedingt ein Muster verwenden möchten. Denken Sie daran, dass eine schlechte Verwendung von Entwurfsmustern dazu führen kann, dass der Code weniger wartbar / lesbar ist.

Das heißt ...

Ihr Fall ist schwierig, da Sie anscheinend zwei Arten der Protokollierung durchführen möchten:

  1. Protokollierung einiger Schritte Ihrer logischen Funktionalität (innen execute)
  2. Protokollieren von Funktionsaufrufen (Eingeben / Verlassen einer Funktion) (außerhalb execute)

Für den ersten Fall müssen Sie im Moment, in dem Sie einige Dinge innerhalb einer Funktion protokollieren möchten, dies tun ... innerhalb der Funktion. Sie könnten das Muster der Vorlagenmethode verwenden, um die Dinge etwas hübscher zu machen, aber ich denke, es ist in diesem Fall übertrieben .

public final class MyBusinessFunction extends BaseFunction {
    @Override
    public final void execute() {
        log.info("Initializing some variables");
        int i = 0;
        double d = 1.0;

        log.info("Starting algorithm");
        for(...) {
            ...
            log.info("One step forward");
            ...
        }
        log.info("Ending algorithm");

        ...
        log.info("Cleaning some mess");
        ...
    }
}

Für den zweiten Fall ist das Dekorationsmuster der richtige Weg:

public final class LoggingFunction extends BaseFunction {
    private final BaseFunction origin;

    public LoggingFunction(BaseFunction origin) {
        this.origin = origin;
    }

    @Override
    public final void execute() {
        log.info("Entering execute");
        origin.execute();
        log.info("Exiting execute");
    }
}

Und Sie könnten es so verwenden in BusinessClass:

public final BusinessClass {
    private final BaseFunction f1;

    public BusinessClass() {
        this.f1 = new LoggingFunction(new MyBusinessFunction());
    }

    public final void doFunction() {
        f1.execute();
    }
}

Ein Anruf bei doFunctionprotokolliert dies:

Entering execute
Initializing some variables
Starting algorithm
One step forward
One step forward
...
Ending algorithm
Cleaning some mess
Exiting execute
Gepunktet
quelle
Würde es funktionieren , die Herkunft Attribut und schreiben wegzulassen super.execute();statt origin.execute();?
Make42
Ihre Lösung ist nicht vollständig - möglicherweise aufgrund meiner schlechten Erklärung. Ich werde der Frage Informationen hinzufügen.
Make42
@ user49283 Warum möchten Sie super.execute()diese Methode aufrufen abstract?
Spotted
@ user49283 Ich habe auch meine Antwort aktualisiert
Spotted
Ich habe das Vorlagenmuster nicht ganz verstanden, aber ich werde es bald noch einmal versuchen. In der Zwischenzeit: Wie wäre es mit dem Befehlsmuster? Ich habe eine Antwort mit einem Befehlsmuster gegeben - bitte kommentieren!
Make42
1

Ihr Ansatz scheint gültig zu sein und ist tatsächlich eine Implementierung des Decorator-Musters . Es gibt andere Möglichkeiten, wie Sie dies tun können, aber Decorator ist ein sehr elegantes und leicht verständliches Muster, das sich sehr gut zur Lösung Ihres Problems eignet.

JDT
quelle
0

Wie wäre es mit dem Befehlsmuster?

abstract class MyLoggingCommandPart {

  MyLoggingCommandPart() {
    log.info("Entering execute part");
    executeWithoutLogging();
    log.info("Exiting execute part");
  }

  abstract void executeWithoutLogging();

}

abstract class MyLoggingCommandWhole {

  MyLoggingCommandWhole() {
    log.info("Entering execute whole");
    executeWithoutLogging();
    log.info("Exiting execute whole");
  }

  abstract void executeWithoutLogging();

}

public final class MyBusinessFunction extends BaseFunction {

    @Override
    public final void execute() {

        new MyLoggingCommandWhole(){

            @Override
            executeWithoutLogging(){

                new MyLoggingCommandPart(){
                    @Override
                    executeWithoutLogging(){
                    // ... what I actually wanne do
                    }
                }

                new MyLoggingCommandPart(){
                    @Override
                    executeWithoutLogging(){
                    // ... some more stuff I actually wanne do
                    }
                }
            }
        }
    }
}
Make42
quelle
Ich habe nicht viel Erfahrung mit dem Befehlsmuster. Der einzige offensichtliche negative Punkt, den ich sehe, ist, dass es kaum lesbar ist (beachten Sie die Einrückungsstufe), daher werde ich es nicht verwenden, weil ich mich gerne um die Leute kümmere, die meinen Code in Zukunft lesen werden.
Spotted
execute()Tun Sie im Moment auch nichts, da Sie nur instanziieren MyLoggingCommandWhole.
Spotted
@Spotted: Ich dachte, dass Instaciating MyLoggingCommandWhole, ausgeführt executeWithoutLogging();von MyLoggingCommandWhole. Ist das nicht der Fall?
Make42
Nein, das ist nicht der Fall. Sie müssten etwas schreiben wie:MyLoggingCommandWhole cmd = new MyLoggingCommandWhole() { ... }; cmd.executeWithoutLogging();
Spotted
@Spotted: Das würde kaum Sinn machen. Wenn es so ist, wie Sie sagen, müsste ich lieber den Code vom Konstruktor in eine Funktion wie executeWithLogging()und einfügen cmd.executeWithLogging(). Immerhin sollte das ausgeführt werden.
Make42