Observer gestütztes Log System

Moritz Willig - 2013-06-18 17:37:31
Nach einiger Zeit poste ich hier mal wieder denn aktuellen Stand des Projekts. Aktuell arbeite ich an einer Hit-Detection welche für die, dann fast vollständige, Spiele Physik verwendet werden soll. Hierzu zerteile ich die Objekte in Spheren, welche deutlich schneller auf einen Treffer getestet werden können. Gleichzeitig habe ich ein Log System geschrieben, welches es erlaubt beliebige Daten zu loggen und darzustellen. Vor allem bei größeren Datenmengen ist es fast unmöglich Fehler mit einem Debugger zu finden, da eine Übersicht über die gesamten Daten oft nicht möglich ist. Auch kann man aus puren Zahlenketten kaum einen Rückschluss über das richtige oder falsche Ergebnis einer Berechnung entscheiden. Deshalb habe ich mich entschieden ein Log System zu implementieren, welches es ermöglicht beliebige Daten darzustellen und beliebig anzuzeigen. Das bedeutet, dass es nun möglich ist, sowohl einzelne Strings und Zahlen zu loggen, als auch die Möglichkeit beinhaltet Punktewolken und Modelle mit mehreren hundert bis tausend Punkten grafisch darzustellen.

Das Observer Pattern

Das Observer Pattern ist ein weit verbreitetes Pattern in der Softwareentwicklung. Es basiert darauf, dass beliebige Observer sich an einem Observer Subject registrieren. Wird nun eine Nachricht vom Observer Subject gesendet, werden alle registrierten Observer informiert. Dieses Pattern eignet sich sehr gut für ein Log System, da wir beliebige Interpreter für unser Log registrieren können und somit die Anzeige der Log Daten fast ohne Aufwand erweitern können.

SourceObserverSubject

Für unsere Zwecke müssen wir die Observer Klassen leicht modifizieren. Da wir nicht nur informiert werden wollen, dass es einen neuen Log Eintrag gibt, sondern auch für die Anzeige die zu loggenden Daten selbst erhalten müssen leiten wir das ObserverSubject ab und fügen das Feld Source hinzu. Dieses ist ein Record, welcher einen Pointer auf die Daten sowie den Typ der Daten enthält.
  TLogSource=class
    source:Pointer;
    Scheme:String;
  end;
Zusätzlich überschreiben und überladen wir die requestNotify Funktion, sodass nun jeder Notify-Aufruf auch das Feld Source setzt.

Default Log

Mit den vorgenommenen Änderungen ist es schon jetzt möglich Daten zu loggen und an die Observer zu verteilen. Da ein Log ohne großen Aufwand und Vorbereitung einzusetzen sein sollte lasse ich über einen Compilerswitch automatisch eine Instanz eines ObserverSubjects erstellen und registriere eine LogForm, welche die Logdaten im Textform ausgibt. Durch das verwendete Observer Pattern kann man auch eine Datei- oder Netzanbindung des Logs realisieren und ist zur Problembehebung nicht mehr an den ausführenden PC gebunden. Ist ein standartmäßiges Log nicht gewünscht (etwa zur Auslieferung der Anwendung) oder soll ein eigenes Log verwendet werden genügt es den Compilerswitch {$DEF DEFAULT_LOG} auszukommentieren.

LogInterpreter

Nachdem wir nun die Daten und den Datentyp erhalten müssen wir nur noch die Daten interpretieren. Dies habe ich über LogInterpreter realisiert, diese Interpreter besitzen einen Eingabe- und Ausgabetyp für Daten. Erhält unser Observer (im Fall mit eingeschalteten default Log unsere ObserverForm) nun eine Lognotification, sucht diese in einer Liste nach Interpretern, welche
  • 1. Ein ankommenden Daten verarbeiten können
  • 2. Die Daten als String zurückgeben
Wird ein Interpreter gefunden, führt dieser die nötige Umwandlung durch und gibt die Daten als String an das Fenster zurück, welches diese nun anzeigen kann. Das folgende Beispiel zeigt ein Codefragment für einen Interpreter, welcher grundlegende Datentypen verarbeitet.
function TLogBasicToStringInterpreter.canProcess(Source: TLogSource): Boolean;
begin
  Result:=(Source.Scheme='string') or
          (Source.Scheme='integer') or
          (Source.Scheme='boolean') or
          ...
end;

procedure TLogBasicToStringInterpreter.interpret(Source: TLogSource;
  inputScheme, outputScheme: String);
begin
...
  if (Source.Scheme='integer') then
    begin
      Output:=IntToStr(Integer(Source.source^));
      exit;
    end;
...
end;
Somit setzt sich das Logsystem wie im folgenden Diagramm dargestellt zusammen (der rote Pfad zeigt den Weg einer Lognachricht):
Delphi - System Log
Delphi - System Log

LogBasicHelper

Wie schon im Diagramm gezeigt habe ich bei meiner Implementierung eine Helferklasse geschrieben, diese hat den großen Vorteil, dass sie das Erzeugen der Sourcerecords übernimmt und gleichzeitig Logfunktionen für Basisdatentypen (string,integer,...) bereitstellt. Somit kapseln wir die gesammte Struktur des Diagramms in einem Befehl zusammen:
log.log('It works');

Moritz Willig


keine Kommentare
Test Content